@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/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
- <${layoutName}>
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
- if (routeFilePath && options.layout !== 'none') {
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}/${options.layout}.astro`;
1754
+ layoutImportPath = `${config.importAliases.layouts}/${layout}.astro`;
1724
1755
  }
1725
1756
  else {
1726
- const layoutFilePath = secureJoin(layoutsRoot, `${options.layout}.astro`);
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(options.layout, layoutImportPath, featureImportPath, featureComponentName, routeExtension);
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: options.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
- if (section) {
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
- const routeFilePath = secureJoin(pagesRoot, routeFileName);
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
- console.log('No files to delete.');
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
- // Update component name in JSX
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
- await promises.writeFile(toRoutePath, content, 'utf-8');
2365
+ changed = true;
2248
2366
  }
2249
2367
  if (config.importAliases.features) {
2250
- if (normalizedFromFeature !== targetFeature) {
2251
- const oldAliasPath = `${config.importAliases.features}/${normalizedFromFeature}`;
2252
- const newAliasPath = `${config.importAliases.features}/${targetFeature}`;
2253
- const ext = config.naming.featureExtension === '.astro' ? '.astro' : '';
2254
- // Replace both the path and the component name if they are different
2255
- await updateSignature(toRoutePath, `import ${fromFeatureComponentName} from '${oldAliasPath}/${fromFeatureComponentName}${ext}'`, `import ${toFeatureComponentName} from '${newAliasPath}/${toFeatureComponentName}${ext}'`);
2256
- // Fallback for prefix only replacement
2257
- await updateSignature(toRoutePath, oldAliasPath, newAliasPath);
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 (fromFeatureComponentName !== toFeatureComponentName) {
2260
- // Name changed but path didn't
2261
- const aliasPath = `${config.importAliases.features}/${targetFeature}`;
2262
- const ext = config.naming.featureExtension === '.astro' ? '.astro' : '';
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(fromRoutePath, fromFeatureDirPath);
2389
+ const oldRelativeDir = getRelativeImportPath(toRoutePath, fromFeatureDirPath);
2268
2390
  const newRelativeDir = getRelativeImportPath(toRoutePath, toFeatureDirPath);
2269
- const ext = config.naming.featureExtension === '.astro' ? '.astro' : '';
2270
- const oldImportPath = `import ${fromFeatureComponentName} from '${oldRelativeDir}/${fromFeatureComponentName}${ext}'`;
2271
- const newImportPath = `import ${toFeatureComponentName} from '${newRelativeDir}/${toFeatureComponentName}${ext}'`;
2272
- if (oldImportPath !== newImportPath) {
2273
- await updateSignature(toRoutePath, oldImportPath, newImportPath);
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
- console.log(`✓ Deleted component: ${componentDir}/`);
2819
- await cleanupEmptyDirs(path.dirname(componentDir), path.join(process.cwd(), config.paths.components));
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 + '/';