@oamm/textor 1.0.12 → 1.0.14
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 +35 -0
- package/dist/bin/textor.js +668 -434
- package/dist/bin/textor.js.map +1 -1
- package/dist/index.cjs +646 -384
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +644 -385
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -6,6 +6,7 @@ var path = require('path');
|
|
|
6
6
|
var crypto = require('crypto');
|
|
7
7
|
var child_process = require('child_process');
|
|
8
8
|
var util = require('util');
|
|
9
|
+
var readline = require('readline');
|
|
9
10
|
|
|
10
11
|
const CONFIG_DIR$1 = '.textor';
|
|
11
12
|
const CONFIG_FILE = 'config.json';
|
|
@@ -497,6 +498,8 @@ function normalizeRoute(route) {
|
|
|
497
498
|
}
|
|
498
499
|
function routeToFilePath(route, options = {}) {
|
|
499
500
|
const { extension = '.astro', mode = 'flat', indexFile = 'index.astro' } = options;
|
|
501
|
+
if (!route)
|
|
502
|
+
return null;
|
|
500
503
|
const normalized = normalizeRoute(route);
|
|
501
504
|
if (normalized === '/') {
|
|
502
505
|
return indexFile;
|
|
@@ -634,14 +637,6 @@ async function safeDelete(filePath, options = {}) {
|
|
|
634
637
|
await promises.unlink(filePath);
|
|
635
638
|
return { deleted: true };
|
|
636
639
|
}
|
|
637
|
-
async function ensureNotExists(filePath, force = false) {
|
|
638
|
-
if (fs.existsSync(filePath)) {
|
|
639
|
-
if (!force) {
|
|
640
|
-
throw new Error(`File already exists: ${filePath}\n` +
|
|
641
|
-
`Use --force to overwrite.`);
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
640
|
async function ensureDir(dirPath) {
|
|
646
641
|
await promises.mkdir(dirPath, { recursive: true });
|
|
647
642
|
}
|
|
@@ -715,6 +710,10 @@ async function safeMove(fromPath, toPath, options = {}) {
|
|
|
715
710
|
if (!fs.existsSync(fromPath)) {
|
|
716
711
|
throw new Error(`Source file not found: ${fromPath}`);
|
|
717
712
|
}
|
|
713
|
+
if (path.resolve(fromPath) === path.resolve(toPath)) {
|
|
714
|
+
const content = await promises.readFile(toPath, 'utf-8');
|
|
715
|
+
return calculateHash(content, normalization);
|
|
716
|
+
}
|
|
718
717
|
if (fs.existsSync(toPath) && !force) {
|
|
719
718
|
throw new Error(`Destination already exists: ${toPath}\n` +
|
|
720
719
|
`Use --force to overwrite.`);
|
|
@@ -1789,29 +1788,35 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1789
1788
|
}
|
|
1790
1789
|
}
|
|
1791
1790
|
}
|
|
1792
|
-
|
|
1793
|
-
if (
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
if (
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1791
|
+
const featureExists = fs.existsSync(featureFilePath);
|
|
1792
|
+
if (featureExists && !options.force) {
|
|
1793
|
+
console.log(`ℹ Feature already exists at ${featureFilePath}. Entering additive mode.`);
|
|
1794
|
+
}
|
|
1795
|
+
// Check sub-items only if not in force mode
|
|
1796
|
+
if (!options.force) {
|
|
1797
|
+
if (shouldCreateIndex && fs.existsSync(indexFilePath))
|
|
1798
|
+
console.log(` - Skipping existing index: ${indexFilePath}`);
|
|
1799
|
+
if (shouldCreateContext && fs.existsSync(contextFilePath))
|
|
1800
|
+
console.log(` - Skipping existing context: ${contextFilePath}`);
|
|
1801
|
+
if (shouldCreateHooks && fs.existsSync(hookFilePath))
|
|
1802
|
+
console.log(` - Skipping existing hook: ${hookFilePath}`);
|
|
1803
|
+
if (shouldCreateTests && fs.existsSync(testFilePath))
|
|
1804
|
+
console.log(` - Skipping existing test: ${testFilePath}`);
|
|
1805
|
+
if (shouldCreateTypes && fs.existsSync(typesFilePath))
|
|
1806
|
+
console.log(` - Skipping existing types: ${typesFilePath}`);
|
|
1807
|
+
if (shouldCreateApi && fs.existsSync(apiFilePath))
|
|
1808
|
+
console.log(` - Skipping existing api: ${apiFilePath}`);
|
|
1809
|
+
if (shouldCreateServices && fs.existsSync(servicesFilePath))
|
|
1810
|
+
console.log(` - Skipping existing services: ${servicesFilePath}`);
|
|
1811
|
+
if (shouldCreateSchemas && fs.existsSync(schemasFilePath))
|
|
1812
|
+
console.log(` - Skipping existing schemas: ${schemasFilePath}`);
|
|
1813
|
+
if (shouldCreateReadme && fs.existsSync(readmeFilePath))
|
|
1814
|
+
console.log(` - Skipping existing readme: ${readmeFilePath}`);
|
|
1815
|
+
if (shouldCreateStories && fs.existsSync(storiesFilePath))
|
|
1816
|
+
console.log(` - Skipping existing stories: ${storiesFilePath}`);
|
|
1817
|
+
if (shouldCreateScriptsDir && fs.existsSync(scriptsIndexPath))
|
|
1818
|
+
console.log(` - Skipping existing scripts: ${scriptsIndexPath}`);
|
|
1819
|
+
}
|
|
1815
1820
|
let layoutImportPath = null;
|
|
1816
1821
|
const cliProps = options.prop || {};
|
|
1817
1822
|
const rawLayoutProps = { ...configLayoutProps, ...cliProps };
|
|
@@ -1933,15 +1938,17 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1933
1938
|
if (shouldCreateTypes)
|
|
1934
1939
|
await ensureDir(typesDirInside);
|
|
1935
1940
|
const featureSignature = getSignature(config, config.naming.featureExtension === '.astro' ? 'astro' : 'tsx');
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1941
|
+
if (!featureExists || options.force) {
|
|
1942
|
+
const featureHash = await writeFileWithSignature(featureFilePath, featureContent, featureSignature, config.hashing?.normalization);
|
|
1943
|
+
await registerFile(featureFilePath, {
|
|
1944
|
+
kind: 'feature',
|
|
1945
|
+
template: 'feature',
|
|
1946
|
+
hash: featureHash,
|
|
1947
|
+
owner: normalizedRoute
|
|
1948
|
+
});
|
|
1949
|
+
writtenFiles.push(featureFilePath);
|
|
1950
|
+
}
|
|
1951
|
+
if (shouldCreateScriptsDir && (!fs.existsSync(scriptsIndexPath) || options.force)) {
|
|
1945
1952
|
const hash = await writeFileWithSignature(scriptsIndexPath, generateScriptsIndexTemplate(), getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
1946
1953
|
await registerFile(scriptsIndexPath, {
|
|
1947
1954
|
kind: 'feature-file',
|
|
@@ -1951,7 +1958,7 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1951
1958
|
});
|
|
1952
1959
|
writtenFiles.push(scriptsIndexPath);
|
|
1953
1960
|
}
|
|
1954
|
-
if (shouldCreateIndex) {
|
|
1961
|
+
if (shouldCreateIndex && (!fs.existsSync(indexFilePath) || options.force)) {
|
|
1955
1962
|
const indexContent = generateIndexTemplate(featureComponentName, config.naming.featureExtension);
|
|
1956
1963
|
const hash = await writeFileWithSignature(indexFilePath, indexContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
1957
1964
|
await registerFile(indexFilePath, {
|
|
@@ -1962,7 +1969,7 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1962
1969
|
});
|
|
1963
1970
|
writtenFiles.push(indexFilePath);
|
|
1964
1971
|
}
|
|
1965
|
-
if (shouldCreateApi) {
|
|
1972
|
+
if (shouldCreateApi && (!fs.existsSync(apiFilePath) || options.force)) {
|
|
1966
1973
|
const apiContent = generateApiTemplate(featureComponentName);
|
|
1967
1974
|
const hash = await writeFileWithSignature(apiFilePath, apiContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
1968
1975
|
await registerFile(apiFilePath, {
|
|
@@ -1973,7 +1980,7 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1973
1980
|
});
|
|
1974
1981
|
writtenFiles.push(apiFilePath);
|
|
1975
1982
|
}
|
|
1976
|
-
if (shouldCreateServices) {
|
|
1983
|
+
if (shouldCreateServices && (!fs.existsSync(servicesFilePath) || options.force)) {
|
|
1977
1984
|
const servicesContent = generateServiceTemplate(featureComponentName);
|
|
1978
1985
|
const hash = await writeFileWithSignature(servicesFilePath, servicesContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
1979
1986
|
await registerFile(servicesFilePath, {
|
|
@@ -1984,7 +1991,7 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1984
1991
|
});
|
|
1985
1992
|
writtenFiles.push(servicesFilePath);
|
|
1986
1993
|
}
|
|
1987
|
-
if (shouldCreateSchemas) {
|
|
1994
|
+
if (shouldCreateSchemas && (!fs.existsSync(schemasFilePath) || options.force)) {
|
|
1988
1995
|
const schemasContent = generateSchemaTemplate(featureComponentName);
|
|
1989
1996
|
const hash = await writeFileWithSignature(schemasFilePath, schemasContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
1990
1997
|
await registerFile(schemasFilePath, {
|
|
@@ -1995,7 +2002,7 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1995
2002
|
});
|
|
1996
2003
|
writtenFiles.push(schemasFilePath);
|
|
1997
2004
|
}
|
|
1998
|
-
if (shouldCreateHooks) {
|
|
2005
|
+
if (shouldCreateHooks && (!fs.existsSync(hookFilePath) || options.force)) {
|
|
1999
2006
|
const hookName = getHookFunctionName(featureComponentName);
|
|
2000
2007
|
const hookContent = generateHookTemplate(featureComponentName, hookName);
|
|
2001
2008
|
const hash = await writeFileWithSignature(hookFilePath, hookContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
@@ -2007,7 +2014,7 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
2007
2014
|
});
|
|
2008
2015
|
writtenFiles.push(hookFilePath);
|
|
2009
2016
|
}
|
|
2010
|
-
if (shouldCreateContext) {
|
|
2017
|
+
if (shouldCreateContext && (!fs.existsSync(contextFilePath) || options.force)) {
|
|
2011
2018
|
const contextContent = generateContextTemplate(featureComponentName);
|
|
2012
2019
|
const hash = await writeFileWithSignature(contextFilePath, contextContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
2013
2020
|
await registerFile(contextFilePath, {
|
|
@@ -2018,7 +2025,7 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
2018
2025
|
});
|
|
2019
2026
|
writtenFiles.push(contextFilePath);
|
|
2020
2027
|
}
|
|
2021
|
-
if (shouldCreateTests) {
|
|
2028
|
+
if (shouldCreateTests && (!fs.existsSync(testFilePath) || options.force)) {
|
|
2022
2029
|
const relativeFeaturePath = `./${path.basename(featureFilePath)}`;
|
|
2023
2030
|
const testContent = generateTestTemplate(featureComponentName, relativeFeaturePath);
|
|
2024
2031
|
const hash = await writeFileWithSignature(testFilePath, testContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
@@ -2030,7 +2037,7 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
2030
2037
|
});
|
|
2031
2038
|
writtenFiles.push(testFilePath);
|
|
2032
2039
|
}
|
|
2033
|
-
if (shouldCreateTypes) {
|
|
2040
|
+
if (shouldCreateTypes && (!fs.existsSync(typesFilePath) || options.force)) {
|
|
2034
2041
|
const typesContent = generateTypesTemplate(featureComponentName);
|
|
2035
2042
|
const hash = await writeFileWithSignature(typesFilePath, typesContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
2036
2043
|
await registerFile(typesFilePath, {
|
|
@@ -2041,7 +2048,7 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
2041
2048
|
});
|
|
2042
2049
|
writtenFiles.push(typesFilePath);
|
|
2043
2050
|
}
|
|
2044
|
-
if (shouldCreateReadme) {
|
|
2051
|
+
if (shouldCreateReadme && (!fs.existsSync(readmeFilePath) || options.force)) {
|
|
2045
2052
|
const readmeContent = generateReadmeTemplate(featureComponentName);
|
|
2046
2053
|
const hash = await writeFileWithSignature(readmeFilePath, readmeContent, getSignature(config, 'astro'), config.hashing?.normalization);
|
|
2047
2054
|
await registerFile(readmeFilePath, {
|
|
@@ -2052,7 +2059,7 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
2052
2059
|
});
|
|
2053
2060
|
writtenFiles.push(readmeFilePath);
|
|
2054
2061
|
}
|
|
2055
|
-
if (shouldCreateStories) {
|
|
2062
|
+
if (shouldCreateStories && (!fs.existsSync(storiesFilePath) || options.force)) {
|
|
2056
2063
|
const relativePath = `./${path.basename(featureFilePath)}`;
|
|
2057
2064
|
const storiesContent = generateStoriesTemplate(featureComponentName, relativePath);
|
|
2058
2065
|
const hash = await writeFileWithSignature(storiesFilePath, storiesContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
@@ -2350,6 +2357,189 @@ async function updateImportsInFile$1(filePath, oldFilePath, newFilePath) {
|
|
|
2350
2357
|
await promises.writeFile(filePath, content, 'utf-8');
|
|
2351
2358
|
}
|
|
2352
2359
|
|
|
2360
|
+
/**
|
|
2361
|
+
* Updates relative imports in a file after it has been moved.
|
|
2362
|
+
*/
|
|
2363
|
+
async function updateImportsInFile(filePath, oldFilePath, newFilePath) {
|
|
2364
|
+
if (!fs.existsSync(filePath))
|
|
2365
|
+
return;
|
|
2366
|
+
let content = await promises.readFile(filePath, 'utf-8');
|
|
2367
|
+
const oldDir = path.dirname(oldFilePath);
|
|
2368
|
+
const newDir = path.dirname(newFilePath);
|
|
2369
|
+
if (oldDir === newDir)
|
|
2370
|
+
return;
|
|
2371
|
+
// Find all relative imports
|
|
2372
|
+
const relativeImportRegex = /from\s+['"](\.\.?\/[^'"]+)['"]/g;
|
|
2373
|
+
let match;
|
|
2374
|
+
const replacements = [];
|
|
2375
|
+
while ((match = relativeImportRegex.exec(content)) !== null) {
|
|
2376
|
+
const relativePath = match[1];
|
|
2377
|
+
const absoluteTarget = path.resolve(oldDir, relativePath);
|
|
2378
|
+
const newRelativePath = getRelativeImportPath(newFilePath, absoluteTarget);
|
|
2379
|
+
replacements.push({
|
|
2380
|
+
full: match[0],
|
|
2381
|
+
oldRel: relativePath,
|
|
2382
|
+
newRel: newRelativePath
|
|
2383
|
+
});
|
|
2384
|
+
}
|
|
2385
|
+
for (const repl of replacements) {
|
|
2386
|
+
content = content.replace(repl.full, `from '${repl.newRel}'`);
|
|
2387
|
+
}
|
|
2388
|
+
await promises.writeFile(filePath, content, 'utf-8');
|
|
2389
|
+
}
|
|
2390
|
+
/**
|
|
2391
|
+
* Moves a directory and its contents, renaming files and updating internal content/imports.
|
|
2392
|
+
*/
|
|
2393
|
+
async function moveDirectory(fromPath, toPath, state, config, options = {}) {
|
|
2394
|
+
const { fromName, toName, owner = null, signatures = [] } = options;
|
|
2395
|
+
if (!fs.existsSync(fromPath)) {
|
|
2396
|
+
throw new Error(`Source directory not found: ${fromPath}`);
|
|
2397
|
+
}
|
|
2398
|
+
if (fs.existsSync(toPath) && !options.force) {
|
|
2399
|
+
throw new Error(`Destination already exists: ${toPath}\n` +
|
|
2400
|
+
`Use --force to overwrite.`);
|
|
2401
|
+
}
|
|
2402
|
+
await ensureDir(toPath);
|
|
2403
|
+
const entries = await promises.readdir(fromPath);
|
|
2404
|
+
for (const entry of entries) {
|
|
2405
|
+
let targetEntry = entry;
|
|
2406
|
+
// Rename files if they match the component name
|
|
2407
|
+
if (fromName && toName && fromName !== toName) {
|
|
2408
|
+
if (entry.includes(fromName)) {
|
|
2409
|
+
targetEntry = entry.replace(fromName, toName);
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
const fromEntryPath = path.join(fromPath, entry);
|
|
2413
|
+
const toEntryPath = path.join(toPath, targetEntry);
|
|
2414
|
+
const stats = await promises.stat(fromEntryPath);
|
|
2415
|
+
if (stats.isDirectory()) {
|
|
2416
|
+
await moveDirectory(fromEntryPath, toEntryPath, state, config, options);
|
|
2417
|
+
}
|
|
2418
|
+
else {
|
|
2419
|
+
const normalizedFromRelative = path.relative(process.cwd(), fromEntryPath).replace(/\\/g, '/');
|
|
2420
|
+
const fileState = state.files[normalizedFromRelative];
|
|
2421
|
+
const newHash = await safeMove(fromEntryPath, toEntryPath, {
|
|
2422
|
+
force: options.force,
|
|
2423
|
+
expectedHash: fileState?.hash,
|
|
2424
|
+
acceptChanges: options.acceptChanges,
|
|
2425
|
+
normalization: config.hashing?.normalization,
|
|
2426
|
+
owner,
|
|
2427
|
+
actualOwner: fileState?.owner,
|
|
2428
|
+
signatures
|
|
2429
|
+
});
|
|
2430
|
+
// Update internal content (signatures, component names) if renaming
|
|
2431
|
+
if (fromName && toName && fromName !== toName) {
|
|
2432
|
+
let content = await promises.readFile(toEntryPath, 'utf-8');
|
|
2433
|
+
let hasChanged = false;
|
|
2434
|
+
// Simple replacement of component names
|
|
2435
|
+
if (content.includes(fromName)) {
|
|
2436
|
+
content = content.replace(new RegExp(fromName, 'g'), toName);
|
|
2437
|
+
hasChanged = true;
|
|
2438
|
+
}
|
|
2439
|
+
// Also handle lowercase class names if any
|
|
2440
|
+
const fromLower = fromName.toLowerCase();
|
|
2441
|
+
const toLower = toName.toLowerCase();
|
|
2442
|
+
if (content.includes(fromLower)) {
|
|
2443
|
+
content = content.replace(new RegExp(fromLower, 'g'), toLower);
|
|
2444
|
+
hasChanged = true;
|
|
2445
|
+
}
|
|
2446
|
+
if (hasChanged) {
|
|
2447
|
+
await promises.writeFile(toEntryPath, content, 'utf-8');
|
|
2448
|
+
// Re-calculate hash after content update
|
|
2449
|
+
const updatedHash = calculateHash(content, config.hashing?.normalization);
|
|
2450
|
+
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
2451
|
+
if (fileState) {
|
|
2452
|
+
state.files[normalizedToRelative] = { ...fileState, hash: updatedHash };
|
|
2453
|
+
delete state.files[normalizedFromRelative];
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
else {
|
|
2457
|
+
// Update state for each file moved normally
|
|
2458
|
+
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
2459
|
+
if (fileState) {
|
|
2460
|
+
state.files[normalizedToRelative] = { ...fileState, hash: newHash };
|
|
2461
|
+
delete state.files[normalizedFromRelative];
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
else {
|
|
2466
|
+
// Update state for each file moved normally
|
|
2467
|
+
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
2468
|
+
if (fileState) {
|
|
2469
|
+
state.files[normalizedToRelative] = { ...fileState, hash: newHash };
|
|
2470
|
+
delete state.files[normalizedFromRelative];
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
const remainingFiles = await promises.readdir(fromPath);
|
|
2476
|
+
if (remainingFiles.length === 0) {
|
|
2477
|
+
await promises.rmdir(fromPath);
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Scans the project and replaces imports of a moved/renamed item.
|
|
2482
|
+
*/
|
|
2483
|
+
async function scanAndReplaceImports(config, state, fromInfo, toInfo, options) {
|
|
2484
|
+
const { fromPath: fromItemPath, fromName, type } = fromInfo;
|
|
2485
|
+
const { toPath: toItemPath, toName } = toInfo;
|
|
2486
|
+
const allFiles = new Set();
|
|
2487
|
+
await scanDirectory(process.cwd(), allFiles);
|
|
2488
|
+
const rootPath = resolvePath(config, type === 'component' ? 'components' : 'features');
|
|
2489
|
+
for (const relPath of allFiles) {
|
|
2490
|
+
const fullPath = path.resolve(process.cwd(), relPath);
|
|
2491
|
+
// Skip the moved directory itself
|
|
2492
|
+
const toFullPath = path.resolve(toItemPath);
|
|
2493
|
+
if (fullPath.startsWith(toFullPath))
|
|
2494
|
+
continue;
|
|
2495
|
+
let content = await promises.readFile(fullPath, 'utf-8');
|
|
2496
|
+
let changed = false;
|
|
2497
|
+
const aliasBase = config.importAliases[type === 'component' ? 'components' : 'features'];
|
|
2498
|
+
const ext = type === 'component' ? '' : (config.naming.featureExtension === '.astro' ? '.astro' : '');
|
|
2499
|
+
if (aliasBase) {
|
|
2500
|
+
const oldAlias = `${aliasBase}/${fromItemPath}`;
|
|
2501
|
+
const newAlias = `${aliasBase}/${toItemPath}`;
|
|
2502
|
+
const oldFullImport = `from '${oldAlias}/${fromName}${ext}'`;
|
|
2503
|
+
const newFullImport = `from '${newAlias}/${toName}${ext}'`;
|
|
2504
|
+
if (content.includes(oldFullImport)) {
|
|
2505
|
+
content = content.replace(new RegExp(oldFullImport.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newFullImport);
|
|
2506
|
+
changed = true;
|
|
2507
|
+
}
|
|
2508
|
+
else if (content.includes(oldAlias)) {
|
|
2509
|
+
content = content.replace(new RegExp(oldAlias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newAlias);
|
|
2510
|
+
changed = true;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
else {
|
|
2514
|
+
const oldDir = path.resolve(rootPath, fromItemPath);
|
|
2515
|
+
const newDir = path.resolve(rootPath, toItemPath);
|
|
2516
|
+
const oldRelPath = getRelativeImportPath(fullPath, oldDir);
|
|
2517
|
+
const newRelPath = getRelativeImportPath(fullPath, newDir);
|
|
2518
|
+
const oldImport = `'${oldRelPath}/${fromName}${ext}'`;
|
|
2519
|
+
const newImport = `'${newRelPath}/${toName}${ext}'`;
|
|
2520
|
+
if (content.includes(oldImport)) {
|
|
2521
|
+
content = content.replace(new RegExp(oldImport.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newImport);
|
|
2522
|
+
changed = true;
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
if (fromName !== toName && changed) {
|
|
2526
|
+
content = content.replace(new RegExp(`\\b${fromName}\\b`, 'g'), toName);
|
|
2527
|
+
}
|
|
2528
|
+
if (changed) {
|
|
2529
|
+
if (options.dryRun) {
|
|
2530
|
+
console.log(` [Scan] Would update imports in ${relPath}`);
|
|
2531
|
+
}
|
|
2532
|
+
else {
|
|
2533
|
+
await promises.writeFile(fullPath, content, 'utf-8');
|
|
2534
|
+
console.log(` [Scan] Updated imports in ${relPath}`);
|
|
2535
|
+
if (state.files[relPath]) {
|
|
2536
|
+
state.files[relPath].hash = calculateHash(content, config.hashing?.normalization);
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2353
2543
|
/**
|
|
2354
2544
|
* Move a section (route + feature).
|
|
2355
2545
|
*
|
|
@@ -2374,40 +2564,53 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2374
2564
|
let actualFromFeature = fromFeature;
|
|
2375
2565
|
let actualToRoute = toRoute;
|
|
2376
2566
|
let actualToFeature = toFeature;
|
|
2377
|
-
// Shift arguments if using state
|
|
2567
|
+
// Shift arguments if using state or if called with fewer arguments
|
|
2378
2568
|
if (!toRoute && fromRoute && fromFeature) {
|
|
2379
2569
|
// textor move-section /old-route /new-route
|
|
2380
|
-
|
|
2570
|
+
actualFromRoute = fromRoute;
|
|
2571
|
+
actualToRoute = fromFeature;
|
|
2572
|
+
actualFromFeature = undefined;
|
|
2573
|
+
actualToFeature = undefined;
|
|
2574
|
+
}
|
|
2575
|
+
// Lookup missing info from state
|
|
2576
|
+
if (actualFromRoute && !actualFromFeature) {
|
|
2577
|
+
const section = findSection(state, actualFromRoute);
|
|
2381
2578
|
if (section) {
|
|
2382
|
-
actualFromRoute = section.route;
|
|
2383
2579
|
actualFromFeature = section.featurePath;
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
}
|
|
2405
|
-
else {
|
|
2406
|
-
// Otherwise just keep it the same
|
|
2407
|
-
actualToFeature = actualFromFeature;
|
|
2408
|
-
}
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
else if (!actualFromRoute && actualFromFeature) {
|
|
2583
|
+
const section = findSection(state, actualFromFeature);
|
|
2584
|
+
if (section) {
|
|
2585
|
+
actualFromRoute = section.route;
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
// If toFeature is not provided, try to derive it from the new route if route moved
|
|
2589
|
+
if (!actualToFeature && actualToRoute && actualFromRoute && actualFromRoute !== actualToRoute && actualFromFeature) {
|
|
2590
|
+
const oldRouteParts = actualFromRoute.split('/').filter(Boolean);
|
|
2591
|
+
const newRouteParts = actualToRoute.split('/').filter(Boolean);
|
|
2592
|
+
const oldFeatureParts = actualFromFeature.split('/').filter(Boolean);
|
|
2593
|
+
let match = true;
|
|
2594
|
+
for (let i = 0; i < oldRouteParts.length; i++) {
|
|
2595
|
+
const routePart = oldRouteParts[i].toLowerCase();
|
|
2596
|
+
const featurePart = oldFeatureParts[i] ? oldFeatureParts[i].toLowerCase() : null;
|
|
2597
|
+
if (featurePart !== routePart) {
|
|
2598
|
+
match = false;
|
|
2599
|
+
break;
|
|
2409
2600
|
}
|
|
2410
2601
|
}
|
|
2602
|
+
if (match && oldRouteParts.length > 0) {
|
|
2603
|
+
actualToFeature = [...newRouteParts, ...oldFeatureParts.slice(oldRouteParts.length)].join('/');
|
|
2604
|
+
}
|
|
2605
|
+
else {
|
|
2606
|
+
actualToFeature = actualFromFeature;
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
else if (!actualToFeature) {
|
|
2610
|
+
actualToFeature = actualFromFeature;
|
|
2611
|
+
}
|
|
2612
|
+
if (!actualToRoute) {
|
|
2613
|
+
actualToRoute = actualFromRoute;
|
|
2411
2614
|
}
|
|
2412
2615
|
const isRouteOnly = options.keepFeature || (!actualToFeature && actualToRoute && !actualFromFeature);
|
|
2413
2616
|
if (isRouteOnly && !actualToRoute) {
|
|
@@ -2432,12 +2635,14 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2432
2635
|
mode: config.routing.mode,
|
|
2433
2636
|
indexFile: config.routing.indexFile
|
|
2434
2637
|
});
|
|
2435
|
-
const fromRoutePath = secureJoin(pagesRoot, fromRouteFile);
|
|
2436
|
-
const toRoutePath = secureJoin(pagesRoot, toRouteFile);
|
|
2638
|
+
const fromRoutePath = fromRouteFile ? secureJoin(pagesRoot, fromRouteFile) : null;
|
|
2639
|
+
const toRoutePath = toRouteFile ? secureJoin(pagesRoot, toRouteFile) : null;
|
|
2437
2640
|
const movedFiles = [];
|
|
2438
2641
|
if (options.dryRun) {
|
|
2439
2642
|
console.log('Dry run - would move:');
|
|
2440
|
-
|
|
2643
|
+
if (fromRoutePath && toRoutePath && fromRoutePath !== toRoutePath) {
|
|
2644
|
+
console.log(` Route: ${fromRoutePath} -> ${toRoutePath}`);
|
|
2645
|
+
}
|
|
2441
2646
|
if (!isRouteOnly && normalizedFromFeature && normalizedToFeature) {
|
|
2442
2647
|
const fromFeaturePath = secureJoin(featuresRoot, normalizedFromFeature);
|
|
2443
2648
|
const toFeaturePath = secureJoin(featuresRoot, normalizedToFeature);
|
|
@@ -2445,81 +2650,90 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2445
2650
|
}
|
|
2446
2651
|
return;
|
|
2447
2652
|
}
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
const fromFeatureComponentName = getFeatureComponentName(normalizedFromFeature);
|
|
2471
|
-
const toFeatureComponentName = getFeatureComponentName(targetFeature);
|
|
2472
|
-
// First, update all relative imports in the file because it moved
|
|
2473
|
-
await updateImportsInFile(toRoutePath, fromRoutePath, toRoutePath);
|
|
2474
|
-
let content = await promises.readFile(toRoutePath, 'utf-8');
|
|
2475
|
-
let changed = false;
|
|
2476
|
-
// Update component name in JSX tags
|
|
2477
|
-
if (fromFeatureComponentName !== toFeatureComponentName) {
|
|
2478
|
-
content = content.replace(new RegExp(`<${fromFeatureComponentName}`, 'g'), `<${toFeatureComponentName}`);
|
|
2479
|
-
content = content.replace(new RegExp(`</${fromFeatureComponentName}`, 'g'), `</${toFeatureComponentName}`);
|
|
2480
|
-
changed = true;
|
|
2653
|
+
let normalizedToRouteRelative = null;
|
|
2654
|
+
if (fromRoutePath && toRoutePath) {
|
|
2655
|
+
const normalizedFromRouteRelative = path.relative(process.cwd(), fromRoutePath).replace(/\\/g, '/');
|
|
2656
|
+
const routeFileState = state.files[normalizedFromRouteRelative];
|
|
2657
|
+
const newRouteHash = await safeMove(fromRoutePath, toRoutePath, {
|
|
2658
|
+
force: options.force,
|
|
2659
|
+
expectedHash: routeFileState?.hash,
|
|
2660
|
+
acceptChanges: options.acceptChanges,
|
|
2661
|
+
owner: normalizedFromRoute,
|
|
2662
|
+
actualOwner: routeFileState?.owner,
|
|
2663
|
+
signatures: configSignatures
|
|
2664
|
+
});
|
|
2665
|
+
if (fromRoutePath !== toRoutePath) {
|
|
2666
|
+
movedFiles.push({ from: fromRoutePath, to: toRoutePath });
|
|
2667
|
+
}
|
|
2668
|
+
// Update state for moved route file
|
|
2669
|
+
normalizedToRouteRelative = path.relative(process.cwd(), toRoutePath).replace(/\\/g, '/');
|
|
2670
|
+
if (routeFileState) {
|
|
2671
|
+
state.files[normalizedToRouteRelative] = { ...routeFileState, hash: newRouteHash };
|
|
2672
|
+
if (fromRoutePath !== toRoutePath) {
|
|
2673
|
+
delete state.files[normalizedFromRouteRelative];
|
|
2674
|
+
}
|
|
2481
2675
|
}
|
|
2482
|
-
if
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
const
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2676
|
+
// Update imports in the route file (even if it didn't move, as feature might have)
|
|
2677
|
+
const targetFeature = normalizedToFeature || normalizedFromFeature;
|
|
2678
|
+
if (targetFeature && fs.existsSync(toRoutePath)) {
|
|
2679
|
+
const fromFeatureDirPath = secureJoin(featuresRoot, normalizedFromFeature);
|
|
2680
|
+
const toFeatureDirPath = secureJoin(featuresRoot, targetFeature);
|
|
2681
|
+
const fromFeatureComponentName = getFeatureComponentName(normalizedFromFeature);
|
|
2682
|
+
const toFeatureComponentName = getFeatureComponentName(targetFeature);
|
|
2683
|
+
// First, update all relative imports in the file because it moved (or stayed)
|
|
2684
|
+
await updateImportsInFile(toRoutePath, fromRoutePath, toRoutePath);
|
|
2685
|
+
let content = await promises.readFile(toRoutePath, 'utf-8');
|
|
2686
|
+
let changed = false;
|
|
2687
|
+
// Update component name in JSX tags
|
|
2688
|
+
if (fromFeatureComponentName !== toFeatureComponentName) {
|
|
2689
|
+
content = content.replace(new RegExp(`<${fromFeatureComponentName}`, 'g'), `<${toFeatureComponentName}`);
|
|
2690
|
+
content = content.replace(new RegExp(`</${fromFeatureComponentName}`, 'g'), `</${toFeatureComponentName}`);
|
|
2495
2691
|
changed = true;
|
|
2496
2692
|
}
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2693
|
+
if (config.importAliases.features) {
|
|
2694
|
+
const oldAliasPath = `${config.importAliases.features}/${normalizedFromFeature}`;
|
|
2695
|
+
const newAliasPath = `${config.importAliases.features}/${targetFeature}`;
|
|
2696
|
+
// Flexible regex to match import identifier and path with alias
|
|
2697
|
+
const importRegex = new RegExp(`(import\\s+)(${fromFeatureComponentName})(\\s+from\\s+['"])${oldAliasPath}(/[^'"]+)?(['"])`, 'g');
|
|
2698
|
+
if (importRegex.test(content)) {
|
|
2699
|
+
content = content.replace(importRegex, (match, p1, p2, p3, subPath, p5) => {
|
|
2700
|
+
let newSubPath = subPath || '';
|
|
2701
|
+
if (subPath && subPath.includes(fromFeatureComponentName)) {
|
|
2702
|
+
newSubPath = subPath.replace(fromFeatureComponentName, toFeatureComponentName);
|
|
2703
|
+
}
|
|
2704
|
+
return `${p1}${toFeatureComponentName}${p3}${newAliasPath}${newSubPath}${p5}`;
|
|
2705
|
+
});
|
|
2706
|
+
changed = true;
|
|
2707
|
+
}
|
|
2708
|
+
else if (content.includes(oldAliasPath)) {
|
|
2709
|
+
// Fallback for path only replacement
|
|
2710
|
+
content = content.replace(new RegExp(oldAliasPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newAliasPath);
|
|
2711
|
+
changed = true;
|
|
2712
|
+
}
|
|
2501
2713
|
}
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2714
|
+
else {
|
|
2715
|
+
const oldRelativeDir = getRelativeImportPath(toRoutePath, fromFeatureDirPath);
|
|
2716
|
+
const newRelativeDir = getRelativeImportPath(toRoutePath, toFeatureDirPath);
|
|
2717
|
+
// Flexible regex for relative imports
|
|
2718
|
+
const relImportRegex = new RegExp(`(import\\s+)(${fromFeatureComponentName})(\\s+from\\s+['"])${oldRelativeDir}(/[^'"]+)?(['"])`, 'g');
|
|
2719
|
+
if (relImportRegex.test(content)) {
|
|
2720
|
+
content = content.replace(relImportRegex, (match, p1, p2, p3, subPath, p5) => {
|
|
2721
|
+
let newSubPath = subPath || '';
|
|
2722
|
+
if (subPath && subPath.includes(fromFeatureComponentName)) {
|
|
2723
|
+
newSubPath = subPath.replace(fromFeatureComponentName, toFeatureComponentName);
|
|
2724
|
+
}
|
|
2725
|
+
return `${p1}${toFeatureComponentName}${p3}${newRelativeDir}${newSubPath}${p5}`;
|
|
2726
|
+
});
|
|
2727
|
+
changed = true;
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
if (changed) {
|
|
2731
|
+
await promises.writeFile(toRoutePath, content, 'utf-8');
|
|
2732
|
+
// Update hash in state after changes
|
|
2733
|
+
if (state.files[normalizedToRouteRelative]) {
|
|
2734
|
+
state.files[normalizedToRouteRelative].hash = calculateHash(content, config.hashing?.normalization);
|
|
2735
|
+
}
|
|
2517
2736
|
}
|
|
2518
|
-
}
|
|
2519
|
-
if (changed) {
|
|
2520
|
-
await promises.writeFile(toRoutePath, content, 'utf-8');
|
|
2521
|
-
// Update hash in state after changes
|
|
2522
|
-
state.files[normalizedToRouteRelative].hash = calculateHash(content, config.hashing?.normalization);
|
|
2523
2737
|
}
|
|
2524
2738
|
}
|
|
2525
2739
|
if (!isRouteOnly && normalizedFromFeature && normalizedToFeature && normalizedFromFeature !== normalizedToFeature) {
|
|
@@ -2541,14 +2755,17 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2541
2755
|
}
|
|
2542
2756
|
if (options.scan && (normalizedFromFeature || normalizedToFeature)) {
|
|
2543
2757
|
await scanAndReplaceImports(config, state, {
|
|
2544
|
-
|
|
2545
|
-
|
|
2758
|
+
fromPath: normalizedFromFeature,
|
|
2759
|
+
fromName: getFeatureComponentName(normalizedFromFeature),
|
|
2760
|
+
type: 'feature'
|
|
2546
2761
|
}, {
|
|
2547
|
-
|
|
2548
|
-
|
|
2762
|
+
toPath: normalizedToFeature || normalizedFromFeature,
|
|
2763
|
+
toName: getFeatureComponentName(normalizedToFeature || normalizedFromFeature)
|
|
2549
2764
|
}, options);
|
|
2550
2765
|
}
|
|
2551
|
-
|
|
2766
|
+
if (fromRoutePath && toRoutePath && fromRoutePath !== toRoutePath) {
|
|
2767
|
+
await cleanupEmptyDirs(path.dirname(fromRoutePath), pagesRoot);
|
|
2768
|
+
}
|
|
2552
2769
|
console.log('✓ Moved:');
|
|
2553
2770
|
movedFiles.forEach(item => {
|
|
2554
2771
|
console.log(` ${item.from}`);
|
|
@@ -2556,6 +2773,14 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2556
2773
|
});
|
|
2557
2774
|
if (movedFiles.length > 0) {
|
|
2558
2775
|
const existingSection = fromSection;
|
|
2776
|
+
// Update ownership in state if route moved
|
|
2777
|
+
if (normalizedFromRoute && normalizedToRoute && normalizedFromRoute !== normalizedToRoute) {
|
|
2778
|
+
for (const f in state.files) {
|
|
2779
|
+
if (state.files[f].owner === normalizedFromRoute) {
|
|
2780
|
+
state.files[f].owner = normalizedToRoute;
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2559
2784
|
// Update section data in state
|
|
2560
2785
|
state.sections = state.sections.filter(s => s.route !== normalizedFromRoute);
|
|
2561
2786
|
state.sections.push({
|
|
@@ -2576,182 +2801,6 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2576
2801
|
throw error;
|
|
2577
2802
|
}
|
|
2578
2803
|
}
|
|
2579
|
-
async function scanAndReplaceImports(config, state, fromInfo, toInfo, options) {
|
|
2580
|
-
const { fromFeaturePath, fromComponentName } = fromInfo;
|
|
2581
|
-
const { toFeaturePath, toComponentName } = toInfo;
|
|
2582
|
-
const allFiles = new Set();
|
|
2583
|
-
await scanDirectory(process.cwd(), allFiles);
|
|
2584
|
-
const featuresRoot = resolvePath(config, 'features');
|
|
2585
|
-
for (const relPath of allFiles) {
|
|
2586
|
-
const fullPath = path.join(process.cwd(), relPath);
|
|
2587
|
-
// Skip the moved directory itself as it was already handled
|
|
2588
|
-
if (fullPath.startsWith(path.resolve(toFeaturePath)))
|
|
2589
|
-
continue;
|
|
2590
|
-
let content = await promises.readFile(fullPath, 'utf-8');
|
|
2591
|
-
let changed = false;
|
|
2592
|
-
const ext = config.naming.featureExtension === '.astro' ? '.astro' : '';
|
|
2593
|
-
// Handle Aliases
|
|
2594
|
-
if (config.importAliases.features) {
|
|
2595
|
-
const oldAlias = `${config.importAliases.features}/${fromFeaturePath}`;
|
|
2596
|
-
const newAlias = `${config.importAliases.features}/${toFeaturePath}`;
|
|
2597
|
-
// Update component name and path if both changed
|
|
2598
|
-
const oldFullImport = `from '${oldAlias}/${fromComponentName}${ext}'`;
|
|
2599
|
-
const newFullImport = `from '${newAlias}/${toComponentName}${ext}'`;
|
|
2600
|
-
if (content.includes(oldFullImport)) {
|
|
2601
|
-
content = content.replace(new RegExp(oldFullImport.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newFullImport);
|
|
2602
|
-
changed = true;
|
|
2603
|
-
}
|
|
2604
|
-
else if (content.includes(oldAlias)) {
|
|
2605
|
-
content = content.replace(new RegExp(oldAlias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newAlias);
|
|
2606
|
-
changed = true;
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
else {
|
|
2610
|
-
// Handle Relative Imports (more complex)
|
|
2611
|
-
// This is best-effort: we look for imports that resolve to the old feature path
|
|
2612
|
-
const fromFeatureDir = secureJoin(featuresRoot, fromFeaturePath);
|
|
2613
|
-
const toFeatureDir = secureJoin(featuresRoot, toFeaturePath);
|
|
2614
|
-
const oldRelPath = getRelativeImportPath(fullPath, fromFeatureDir);
|
|
2615
|
-
const newRelPath = getRelativeImportPath(fullPath, toFeatureDir);
|
|
2616
|
-
const oldImport = `'${oldRelPath}/${fromComponentName}${ext}'`;
|
|
2617
|
-
const newImport = `'${newRelPath}/${toComponentName}${ext}'`;
|
|
2618
|
-
if (content.includes(oldImport)) {
|
|
2619
|
-
content = content.replace(new RegExp(oldImport.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newImport);
|
|
2620
|
-
changed = true;
|
|
2621
|
-
}
|
|
2622
|
-
}
|
|
2623
|
-
// Update component name in JSX and imports if it changed
|
|
2624
|
-
if (fromComponentName !== toComponentName && changed) {
|
|
2625
|
-
content = content.replace(new RegExp(`\\b${fromComponentName}\\b`, 'g'), toComponentName);
|
|
2626
|
-
}
|
|
2627
|
-
if (changed) {
|
|
2628
|
-
if (options.dryRun) {
|
|
2629
|
-
console.log(` [Scan] Would update imports in ${relPath}`);
|
|
2630
|
-
}
|
|
2631
|
-
else {
|
|
2632
|
-
await promises.writeFile(fullPath, content, 'utf-8');
|
|
2633
|
-
console.log(` [Scan] Updated imports in ${relPath}`);
|
|
2634
|
-
// Update state hash if this file is managed
|
|
2635
|
-
if (state.files[relPath]) {
|
|
2636
|
-
state.files[relPath].hash = calculateHash(content, config.hashing?.normalization);
|
|
2637
|
-
}
|
|
2638
|
-
}
|
|
2639
|
-
}
|
|
2640
|
-
}
|
|
2641
|
-
}
|
|
2642
|
-
async function moveDirectory(fromPath, toPath, state, config, options = {}) {
|
|
2643
|
-
const { fromName, toName, owner = null } = options;
|
|
2644
|
-
if (!fs.existsSync(fromPath)) {
|
|
2645
|
-
throw new Error(`Source directory not found: ${fromPath}`);
|
|
2646
|
-
}
|
|
2647
|
-
if (fs.existsSync(toPath) && !options.force) {
|
|
2648
|
-
throw new Error(`Destination already exists: ${toPath}\n` +
|
|
2649
|
-
`Use --force to overwrite.`);
|
|
2650
|
-
}
|
|
2651
|
-
await ensureDir(toPath);
|
|
2652
|
-
const entries = await promises.readdir(fromPath);
|
|
2653
|
-
for (const entry of entries) {
|
|
2654
|
-
let targetEntry = entry;
|
|
2655
|
-
// Rename files if they match the component name
|
|
2656
|
-
if (fromName && toName && fromName !== toName) {
|
|
2657
|
-
if (entry.includes(fromName)) {
|
|
2658
|
-
targetEntry = entry.replace(fromName, toName);
|
|
2659
|
-
}
|
|
2660
|
-
}
|
|
2661
|
-
const fromEntryPath = path.join(fromPath, entry);
|
|
2662
|
-
const toEntryPath = path.join(toPath, targetEntry);
|
|
2663
|
-
const stats = await promises.stat(fromEntryPath);
|
|
2664
|
-
if (stats.isDirectory()) {
|
|
2665
|
-
await moveDirectory(fromEntryPath, toEntryPath, state, config, options);
|
|
2666
|
-
}
|
|
2667
|
-
else {
|
|
2668
|
-
const normalizedFromRelative = path.relative(process.cwd(), fromEntryPath).replace(/\\/g, '/');
|
|
2669
|
-
const fileState = state.files[normalizedFromRelative];
|
|
2670
|
-
const newHash = await safeMove(fromEntryPath, toEntryPath, {
|
|
2671
|
-
force: options.force,
|
|
2672
|
-
expectedHash: fileState?.hash,
|
|
2673
|
-
acceptChanges: options.acceptChanges,
|
|
2674
|
-
normalization: config.hashing?.normalization,
|
|
2675
|
-
owner,
|
|
2676
|
-
actualOwner: fileState?.owner
|
|
2677
|
-
});
|
|
2678
|
-
// Update internal content (signatures, component names) if renaming
|
|
2679
|
-
if (fromName && toName && fromName !== toName) {
|
|
2680
|
-
let content = await promises.readFile(toEntryPath, 'utf-8');
|
|
2681
|
-
let hasChanged = false;
|
|
2682
|
-
// Simple replacement of component names
|
|
2683
|
-
if (content.includes(fromName)) {
|
|
2684
|
-
content = content.replace(new RegExp(fromName, 'g'), toName);
|
|
2685
|
-
hasChanged = true;
|
|
2686
|
-
}
|
|
2687
|
-
// Also handle lowercase class names if any
|
|
2688
|
-
const fromLower = fromName.toLowerCase();
|
|
2689
|
-
const toLower = toName.toLowerCase();
|
|
2690
|
-
if (content.includes(fromLower)) {
|
|
2691
|
-
content = content.replace(new RegExp(fromLower, 'g'), toLower);
|
|
2692
|
-
hasChanged = true;
|
|
2693
|
-
}
|
|
2694
|
-
if (hasChanged) {
|
|
2695
|
-
await promises.writeFile(toEntryPath, content, 'utf-8');
|
|
2696
|
-
// Re-calculate hash after content update
|
|
2697
|
-
const updatedHash = calculateHash(content, config.hashing?.normalization);
|
|
2698
|
-
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
2699
|
-
if (fileState) {
|
|
2700
|
-
state.files[normalizedToRelative] = { ...fileState, hash: updatedHash };
|
|
2701
|
-
delete state.files[normalizedFromRelative];
|
|
2702
|
-
}
|
|
2703
|
-
}
|
|
2704
|
-
else {
|
|
2705
|
-
// Update state for each file moved normally
|
|
2706
|
-
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
2707
|
-
if (fileState) {
|
|
2708
|
-
state.files[normalizedToRelative] = { ...fileState, hash: newHash };
|
|
2709
|
-
delete state.files[normalizedFromRelative];
|
|
2710
|
-
}
|
|
2711
|
-
}
|
|
2712
|
-
}
|
|
2713
|
-
else {
|
|
2714
|
-
// Update state for each file moved normally
|
|
2715
|
-
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
2716
|
-
if (fileState) {
|
|
2717
|
-
state.files[normalizedToRelative] = { ...fileState, hash: newHash };
|
|
2718
|
-
delete state.files[normalizedFromRelative];
|
|
2719
|
-
}
|
|
2720
|
-
}
|
|
2721
|
-
}
|
|
2722
|
-
}
|
|
2723
|
-
const remainingFiles = await promises.readdir(fromPath);
|
|
2724
|
-
if (remainingFiles.length === 0) {
|
|
2725
|
-
await promises.rmdir(fromPath);
|
|
2726
|
-
}
|
|
2727
|
-
}
|
|
2728
|
-
async function updateImportsInFile(filePath, oldFilePath, newFilePath) {
|
|
2729
|
-
if (!fs.existsSync(filePath))
|
|
2730
|
-
return;
|
|
2731
|
-
let content = await promises.readFile(filePath, 'utf-8');
|
|
2732
|
-
const oldDir = path.dirname(oldFilePath);
|
|
2733
|
-
const newDir = path.dirname(newFilePath);
|
|
2734
|
-
if (oldDir === newDir)
|
|
2735
|
-
return;
|
|
2736
|
-
// Find all relative imports
|
|
2737
|
-
const relativeImportRegex = /from\s+['"](\.\.?\/[^'"]+)['"]/g;
|
|
2738
|
-
let match;
|
|
2739
|
-
const replacements = [];
|
|
2740
|
-
while ((match = relativeImportRegex.exec(content)) !== null) {
|
|
2741
|
-
const relativePath = match[1];
|
|
2742
|
-
const absoluteTarget = path.resolve(oldDir, relativePath);
|
|
2743
|
-
const newRelativePath = getRelativeImportPath(newFilePath, absoluteTarget);
|
|
2744
|
-
replacements.push({
|
|
2745
|
-
full: match[0],
|
|
2746
|
-
oldRel: relativePath,
|
|
2747
|
-
newRel: newRelativePath
|
|
2748
|
-
});
|
|
2749
|
-
}
|
|
2750
|
-
for (const repl of replacements) {
|
|
2751
|
-
content = content.replace(repl.full, `from '${repl.newRel}'`);
|
|
2752
|
-
}
|
|
2753
|
-
await promises.writeFile(filePath, content, 'utf-8');
|
|
2754
|
-
}
|
|
2755
2804
|
|
|
2756
2805
|
async function createComponentCommand(componentName, options) {
|
|
2757
2806
|
try {
|
|
@@ -2823,30 +2872,37 @@ async function createComponentCommand(componentName, options) {
|
|
|
2823
2872
|
console.log(` Sub-components: ${subComponentsDir}/`);
|
|
2824
2873
|
return;
|
|
2825
2874
|
}
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
if
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2875
|
+
const componentExists = fs.existsSync(componentFilePath);
|
|
2876
|
+
if (componentExists && !options.force) {
|
|
2877
|
+
console.log(`ℹ Component already exists at ${componentFilePath}. Entering additive mode.`);
|
|
2878
|
+
}
|
|
2879
|
+
// Check sub-items only if not in force mode
|
|
2880
|
+
if (!options.force) {
|
|
2881
|
+
if (fs.existsSync(indexFilePath))
|
|
2882
|
+
console.log(` - Skipping existing index: ${indexFilePath}`);
|
|
2883
|
+
if (shouldCreateContext && fs.existsSync(contextFilePath))
|
|
2884
|
+
console.log(` - Skipping existing context: ${contextFilePath}`);
|
|
2885
|
+
if (shouldCreateHook && fs.existsSync(hookFilePath))
|
|
2886
|
+
console.log(` - Skipping existing hook: ${hookFilePath}`);
|
|
2887
|
+
if (shouldCreateTests && fs.existsSync(testFilePath))
|
|
2888
|
+
console.log(` - Skipping existing test: ${testFilePath}`);
|
|
2889
|
+
if (shouldCreateConfig && fs.existsSync(configFilePath))
|
|
2890
|
+
console.log(` - Skipping existing config: ${configFilePath}`);
|
|
2891
|
+
if (shouldCreateConstants && fs.existsSync(constantsFilePath))
|
|
2892
|
+
console.log(` - Skipping existing constants: ${constantsFilePath}`);
|
|
2893
|
+
if (shouldCreateTypes && fs.existsSync(typesFilePath))
|
|
2894
|
+
console.log(` - Skipping existing types: ${typesFilePath}`);
|
|
2895
|
+
if (shouldCreateApi && fs.existsSync(apiFilePath))
|
|
2896
|
+
console.log(` - Skipping existing api: ${apiFilePath}`);
|
|
2897
|
+
if (shouldCreateServices && fs.existsSync(servicesFilePath))
|
|
2898
|
+
console.log(` - Skipping existing services: ${servicesFilePath}`);
|
|
2899
|
+
if (shouldCreateSchemas && fs.existsSync(schemasFilePath))
|
|
2900
|
+
console.log(` - Skipping existing schemas: ${schemasFilePath}`);
|
|
2901
|
+
if (shouldCreateReadme && fs.existsSync(readmeFilePath))
|
|
2902
|
+
console.log(` - Skipping existing readme: ${readmeFilePath}`);
|
|
2903
|
+
if (shouldCreateStories && fs.existsSync(storiesFilePath))
|
|
2904
|
+
console.log(` - Skipping existing stories: ${storiesFilePath}`);
|
|
2905
|
+
}
|
|
2850
2906
|
await ensureDir(componentDir);
|
|
2851
2907
|
if (shouldCreateSubComponentsDir)
|
|
2852
2908
|
await ensureDir(subComponentsDir);
|
|
@@ -2870,24 +2926,29 @@ async function createComponentCommand(componentName, options) {
|
|
|
2870
2926
|
await ensureDir(schemasDirInside);
|
|
2871
2927
|
const componentContent = generateComponentTemplate(normalizedName, framework, config.naming.componentExtension);
|
|
2872
2928
|
const signature = getSignature(config, config.naming.componentExtension === '.astro' ? 'astro' : 'tsx');
|
|
2873
|
-
const
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2929
|
+
const writtenFiles = [];
|
|
2930
|
+
if (!componentExists || options.force) {
|
|
2931
|
+
const componentHash = await writeFileWithSignature(componentFilePath, componentContent, signature, config.hashing?.normalization);
|
|
2932
|
+
await registerFile(componentFilePath, {
|
|
2933
|
+
kind: 'component',
|
|
2934
|
+
template: 'component',
|
|
2935
|
+
hash: componentHash,
|
|
2936
|
+
owner: normalizedName
|
|
2937
|
+
});
|
|
2938
|
+
writtenFiles.push(componentFilePath);
|
|
2939
|
+
}
|
|
2940
|
+
if (!fs.existsSync(indexFilePath) || options.force) {
|
|
2941
|
+
const indexContent = generateIndexTemplate(normalizedName, config.naming.componentExtension);
|
|
2942
|
+
const indexHash = await writeFileWithSignature(indexFilePath, indexContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
2943
|
+
await registerFile(indexFilePath, {
|
|
2944
|
+
kind: 'component-file',
|
|
2945
|
+
template: 'index',
|
|
2946
|
+
hash: indexHash,
|
|
2947
|
+
owner: normalizedName
|
|
2948
|
+
});
|
|
2949
|
+
writtenFiles.push(indexFilePath);
|
|
2950
|
+
}
|
|
2951
|
+
if (shouldCreateTypes && (!fs.existsSync(typesFilePath) || options.force)) {
|
|
2891
2952
|
const typesContent = generateTypesTemplate(normalizedName);
|
|
2892
2953
|
const hash = await writeFileWithSignature(typesFilePath, typesContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
2893
2954
|
await registerFile(typesFilePath, {
|
|
@@ -2898,7 +2959,7 @@ async function createComponentCommand(componentName, options) {
|
|
|
2898
2959
|
});
|
|
2899
2960
|
writtenFiles.push(typesFilePath);
|
|
2900
2961
|
}
|
|
2901
|
-
if (shouldCreateContext) {
|
|
2962
|
+
if (shouldCreateContext && (!fs.existsSync(contextFilePath) || options.force)) {
|
|
2902
2963
|
const contextContent = generateContextTemplate(normalizedName);
|
|
2903
2964
|
const hash = await writeFileWithSignature(contextFilePath, contextContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
2904
2965
|
await registerFile(contextFilePath, {
|
|
@@ -2909,7 +2970,7 @@ async function createComponentCommand(componentName, options) {
|
|
|
2909
2970
|
});
|
|
2910
2971
|
writtenFiles.push(contextFilePath);
|
|
2911
2972
|
}
|
|
2912
|
-
if (shouldCreateHook) {
|
|
2973
|
+
if (shouldCreateHook && (!fs.existsSync(hookFilePath) || options.force)) {
|
|
2913
2974
|
const hookName = getHookFunctionName(normalizedName);
|
|
2914
2975
|
const hookContent = generateHookTemplate(normalizedName, hookName);
|
|
2915
2976
|
const hash = await writeFileWithSignature(hookFilePath, hookContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
@@ -2921,7 +2982,7 @@ async function createComponentCommand(componentName, options) {
|
|
|
2921
2982
|
});
|
|
2922
2983
|
writtenFiles.push(hookFilePath);
|
|
2923
2984
|
}
|
|
2924
|
-
if (shouldCreateTests) {
|
|
2985
|
+
if (shouldCreateTests && (!fs.existsSync(testFilePath) || options.force)) {
|
|
2925
2986
|
const relativeComponentPath = `../${normalizedName}${config.naming.componentExtension}`;
|
|
2926
2987
|
const testContent = generateTestTemplate(normalizedName, relativeComponentPath);
|
|
2927
2988
|
const hash = await writeFileWithSignature(testFilePath, testContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
@@ -2933,7 +2994,7 @@ async function createComponentCommand(componentName, options) {
|
|
|
2933
2994
|
});
|
|
2934
2995
|
writtenFiles.push(testFilePath);
|
|
2935
2996
|
}
|
|
2936
|
-
if (shouldCreateConfig) {
|
|
2997
|
+
if (shouldCreateConfig && (!fs.existsSync(configFilePath) || options.force)) {
|
|
2937
2998
|
const configContent = generateConfigTemplate(normalizedName);
|
|
2938
2999
|
const hash = await writeFileWithSignature(configFilePath, configContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
2939
3000
|
await registerFile(configFilePath, {
|
|
@@ -2944,7 +3005,7 @@ async function createComponentCommand(componentName, options) {
|
|
|
2944
3005
|
});
|
|
2945
3006
|
writtenFiles.push(configFilePath);
|
|
2946
3007
|
}
|
|
2947
|
-
if (shouldCreateConstants) {
|
|
3008
|
+
if (shouldCreateConstants && (!fs.existsSync(constantsFilePath) || options.force)) {
|
|
2948
3009
|
const constantsContent = generateConstantsTemplate(normalizedName);
|
|
2949
3010
|
const hash = await writeFileWithSignature(constantsFilePath, constantsContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
2950
3011
|
await registerFile(constantsFilePath, {
|
|
@@ -2955,7 +3016,7 @@ async function createComponentCommand(componentName, options) {
|
|
|
2955
3016
|
});
|
|
2956
3017
|
writtenFiles.push(constantsFilePath);
|
|
2957
3018
|
}
|
|
2958
|
-
if (shouldCreateApi) {
|
|
3019
|
+
if (shouldCreateApi && (!fs.existsSync(apiFilePath) || options.force)) {
|
|
2959
3020
|
const apiContent = generateApiTemplate(normalizedName);
|
|
2960
3021
|
const hash = await writeFileWithSignature(apiFilePath, apiContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
2961
3022
|
await registerFile(apiFilePath, {
|
|
@@ -2966,7 +3027,7 @@ async function createComponentCommand(componentName, options) {
|
|
|
2966
3027
|
});
|
|
2967
3028
|
writtenFiles.push(apiFilePath);
|
|
2968
3029
|
}
|
|
2969
|
-
if (shouldCreateServices) {
|
|
3030
|
+
if (shouldCreateServices && (!fs.existsSync(servicesFilePath) || options.force)) {
|
|
2970
3031
|
const servicesContent = generateServiceTemplate(normalizedName);
|
|
2971
3032
|
const hash = await writeFileWithSignature(servicesFilePath, servicesContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
2972
3033
|
await registerFile(servicesFilePath, {
|
|
@@ -2977,7 +3038,7 @@ async function createComponentCommand(componentName, options) {
|
|
|
2977
3038
|
});
|
|
2978
3039
|
writtenFiles.push(servicesFilePath);
|
|
2979
3040
|
}
|
|
2980
|
-
if (shouldCreateSchemas) {
|
|
3041
|
+
if (shouldCreateSchemas && (!fs.existsSync(schemasFilePath) || options.force)) {
|
|
2981
3042
|
const schemasContent = generateSchemaTemplate(normalizedName);
|
|
2982
3043
|
const hash = await writeFileWithSignature(schemasFilePath, schemasContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
2983
3044
|
await registerFile(schemasFilePath, {
|
|
@@ -2988,7 +3049,7 @@ async function createComponentCommand(componentName, options) {
|
|
|
2988
3049
|
});
|
|
2989
3050
|
writtenFiles.push(schemasFilePath);
|
|
2990
3051
|
}
|
|
2991
|
-
if (shouldCreateReadme) {
|
|
3052
|
+
if (shouldCreateReadme && (!fs.existsSync(readmeFilePath) || options.force)) {
|
|
2992
3053
|
const readmeContent = generateReadmeTemplate(normalizedName);
|
|
2993
3054
|
const hash = await writeFileWithSignature(readmeFilePath, readmeContent, getSignature(config, 'astro'), config.hashing?.normalization);
|
|
2994
3055
|
await registerFile(readmeFilePath, {
|
|
@@ -2999,7 +3060,7 @@ async function createComponentCommand(componentName, options) {
|
|
|
2999
3060
|
});
|
|
3000
3061
|
writtenFiles.push(readmeFilePath);
|
|
3001
3062
|
}
|
|
3002
|
-
if (shouldCreateStories) {
|
|
3063
|
+
if (shouldCreateStories && (!fs.existsSync(storiesFilePath) || options.force)) {
|
|
3003
3064
|
const relativePath = `./${normalizedName}${config.naming.componentExtension}`;
|
|
3004
3065
|
const storiesContent = generateStoriesTemplate(normalizedName, relativePath);
|
|
3005
3066
|
const hash = await writeFileWithSignature(storiesFilePath, storiesContent, getSignature(config, 'typescript'), config.hashing?.normalization);
|
|
@@ -3794,6 +3855,205 @@ async function normalizeStateCommand(options) {
|
|
|
3794
3855
|
}
|
|
3795
3856
|
}
|
|
3796
3857
|
|
|
3858
|
+
/**
|
|
3859
|
+
* Removes missing references from Textor state.
|
|
3860
|
+
* @param {Object} options
|
|
3861
|
+
* @param {boolean} options.dryRun
|
|
3862
|
+
* @param {boolean} options.yes
|
|
3863
|
+
*/
|
|
3864
|
+
async function pruneMissingCommand(options = {}) {
|
|
3865
|
+
try {
|
|
3866
|
+
const config = await loadConfig();
|
|
3867
|
+
const state = await loadState();
|
|
3868
|
+
const results = await getProjectStatus(config, state);
|
|
3869
|
+
if (results.missing.length === 0) {
|
|
3870
|
+
console.log('No missing references found.');
|
|
3871
|
+
return;
|
|
3872
|
+
}
|
|
3873
|
+
console.log(`Found ${results.missing.length} missing references:`);
|
|
3874
|
+
results.missing.forEach(f => console.log(` - ${f}`));
|
|
3875
|
+
if (options.dryRun) {
|
|
3876
|
+
console.log('\nDry run: no changes applied to state.');
|
|
3877
|
+
return;
|
|
3878
|
+
}
|
|
3879
|
+
if (!options.yes && options.interactive !== false && process.stdin.isTTY && process.env.NODE_ENV !== 'test') {
|
|
3880
|
+
const rl = readline.createInterface({
|
|
3881
|
+
input: process.stdin,
|
|
3882
|
+
output: process.stdout
|
|
3883
|
+
});
|
|
3884
|
+
const confirmed = await new Promise(resolve => {
|
|
3885
|
+
rl.question('\nDo you want to proceed with pruning? (y/N) ', (answer) => {
|
|
3886
|
+
rl.close();
|
|
3887
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
3888
|
+
});
|
|
3889
|
+
});
|
|
3890
|
+
if (!confirmed) {
|
|
3891
|
+
console.log('Aborted.');
|
|
3892
|
+
return;
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
for (const relPath of results.missing) {
|
|
3896
|
+
delete state.files[relPath];
|
|
3897
|
+
}
|
|
3898
|
+
// Reconstruct metadata
|
|
3899
|
+
state.components = reconstructComponents(state.files, config);
|
|
3900
|
+
state.sections = reconstructSections(state, config);
|
|
3901
|
+
await saveState(state);
|
|
3902
|
+
console.log(`\n✓ Successfully removed ${results.missing.length} missing references from state.`);
|
|
3903
|
+
}
|
|
3904
|
+
catch (error) {
|
|
3905
|
+
console.error('Error:', error.message);
|
|
3906
|
+
if (typeof process.exit === 'function' && process.env.NODE_ENV !== 'test') {
|
|
3907
|
+
process.exit(1);
|
|
3908
|
+
}
|
|
3909
|
+
throw error;
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
|
|
3913
|
+
/**
|
|
3914
|
+
* Dispatcher for rename commands.
|
|
3915
|
+
*/
|
|
3916
|
+
async function renameCommand(type, oldName, newName, options) {
|
|
3917
|
+
try {
|
|
3918
|
+
if (!type || !oldName || !newName) {
|
|
3919
|
+
throw new Error('Usage: textor rename <route|feature|component> <oldName> <newName>');
|
|
3920
|
+
}
|
|
3921
|
+
if (type === 'route' || type === 'path') {
|
|
3922
|
+
const normalizedOld = normalizeRoute(oldName);
|
|
3923
|
+
const normalizedNew = normalizeRoute(newName);
|
|
3924
|
+
// By default, move-section will try to move the feature if it matches the route.
|
|
3925
|
+
// For a simple "rename route", we might want to keep that behavior or not.
|
|
3926
|
+
// Usually "rename route" means just the URL/file.
|
|
3927
|
+
return await moveSectionCommand(normalizedOld, undefined, normalizedNew, undefined, options);
|
|
3928
|
+
}
|
|
3929
|
+
if (type === 'feature') {
|
|
3930
|
+
const state = await loadState();
|
|
3931
|
+
const normalizedOld = featureToDirectoryPath(oldName);
|
|
3932
|
+
const normalizedNew = featureToDirectoryPath(newName);
|
|
3933
|
+
const section = findSection(state, normalizedOld);
|
|
3934
|
+
if (section) {
|
|
3935
|
+
// If it's a managed section, move it using section logic
|
|
3936
|
+
return await moveSectionCommand(section.route, section.featurePath, section.route, normalizedNew, options);
|
|
3937
|
+
}
|
|
3938
|
+
else {
|
|
3939
|
+
// Standalone feature move
|
|
3940
|
+
return await moveSectionCommand(undefined, normalizedOld, undefined, normalizedNew, options);
|
|
3941
|
+
}
|
|
3942
|
+
}
|
|
3943
|
+
if (type === 'component') {
|
|
3944
|
+
return await renameComponent(oldName, newName, options);
|
|
3945
|
+
}
|
|
3946
|
+
throw new Error(`Unknown rename type: ${type}. Supported types: route, feature, component.`);
|
|
3947
|
+
}
|
|
3948
|
+
catch (error) {
|
|
3949
|
+
console.error('Error:', error.message);
|
|
3950
|
+
if (typeof process.exit === 'function' && process.env.NODE_ENV !== 'test') {
|
|
3951
|
+
process.exit(1);
|
|
3952
|
+
}
|
|
3953
|
+
throw error;
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
/**
|
|
3957
|
+
* Specialized logic for renaming shared components.
|
|
3958
|
+
*/
|
|
3959
|
+
async function renameComponent(oldName, newName, options) {
|
|
3960
|
+
const config = await loadConfig();
|
|
3961
|
+
const state = await loadState();
|
|
3962
|
+
const normalizedOldName = normalizeComponentName(oldName);
|
|
3963
|
+
const normalizedNewName = normalizeComponentName(newName);
|
|
3964
|
+
const component = findComponent(state, normalizedOldName);
|
|
3965
|
+
const componentsRoot = resolvePath(config, 'components');
|
|
3966
|
+
const fromPath = component
|
|
3967
|
+
? path.resolve(process.cwd(), component.path)
|
|
3968
|
+
: path.join(componentsRoot, normalizedOldName);
|
|
3969
|
+
const toPath = path.join(componentsRoot, normalizedNewName);
|
|
3970
|
+
if (options.dryRun) {
|
|
3971
|
+
console.log(`Dry run - would rename component: ${normalizedOldName} -> ${normalizedNewName}`);
|
|
3972
|
+
console.log(` Path: ${fromPath} -> ${toPath}`);
|
|
3973
|
+
return;
|
|
3974
|
+
}
|
|
3975
|
+
const signatures = Object.values(config.signatures || {});
|
|
3976
|
+
await moveDirectory(fromPath, toPath, state, config, {
|
|
3977
|
+
...options,
|
|
3978
|
+
fromName: normalizedOldName,
|
|
3979
|
+
toName: normalizedNewName,
|
|
3980
|
+
signatures
|
|
3981
|
+
});
|
|
3982
|
+
if (options.scan) {
|
|
3983
|
+
await scanAndReplaceImports(config, state, {
|
|
3984
|
+
fromPath: normalizedOldName,
|
|
3985
|
+
fromName: normalizedOldName,
|
|
3986
|
+
type: 'component'
|
|
3987
|
+
}, {
|
|
3988
|
+
toPath: normalizedNewName,
|
|
3989
|
+
toName: normalizedNewName
|
|
3990
|
+
}, options);
|
|
3991
|
+
}
|
|
3992
|
+
await cleanupEmptyDirs(path.dirname(fromPath), componentsRoot);
|
|
3993
|
+
// Update state metadata
|
|
3994
|
+
if (component) {
|
|
3995
|
+
component.name = normalizedNewName;
|
|
3996
|
+
component.path = path.relative(process.cwd(), toPath).replace(/\\/g, '/');
|
|
3997
|
+
}
|
|
3998
|
+
await saveState(state);
|
|
3999
|
+
console.log(`✓ Renamed component ${normalizedOldName} to ${normalizedNewName}`);
|
|
4000
|
+
}
|
|
4001
|
+
|
|
4002
|
+
/**
|
|
4003
|
+
* Add a new item (hook, api, service, etc.) to an existing feature or component.
|
|
4004
|
+
*
|
|
4005
|
+
* @param {string} itemType The type of item to add (e.g., 'api', 'hook', 'service')
|
|
4006
|
+
* @param {string} targetName The name of the feature or component
|
|
4007
|
+
* @param {Object} options Additional options from Commander
|
|
4008
|
+
*/
|
|
4009
|
+
async function addItemCommand(itemType, targetName, options) {
|
|
4010
|
+
try {
|
|
4011
|
+
const state = await loadState();
|
|
4012
|
+
// Normalize itemType
|
|
4013
|
+
let normalizedItem = itemType.toLowerCase();
|
|
4014
|
+
if (normalizedItem === 'test')
|
|
4015
|
+
normalizedItem = 'tests';
|
|
4016
|
+
if (normalizedItem === 'service')
|
|
4017
|
+
normalizedItem = 'services';
|
|
4018
|
+
if (normalizedItem === 'schema')
|
|
4019
|
+
normalizedItem = 'schemas';
|
|
4020
|
+
if (normalizedItem === 'hook')
|
|
4021
|
+
normalizedItem = 'hooks'; // for add-section
|
|
4022
|
+
// Try to find as section (feature) first
|
|
4023
|
+
let section = findSection(state, targetName);
|
|
4024
|
+
let component = findComponent(state, targetName);
|
|
4025
|
+
// If not found by exact name, try to find by featurePath or part of it
|
|
4026
|
+
if (!section && !component) {
|
|
4027
|
+
section = state.sections.find(s => s.featurePath === targetName || s.featurePath.endsWith('/' + targetName));
|
|
4028
|
+
}
|
|
4029
|
+
if (!section && !component) {
|
|
4030
|
+
throw new Error(`Target not found in state: "${targetName}". Please use "add-section" or "create-component" directly if it's not managed by Textor.`);
|
|
4031
|
+
}
|
|
4032
|
+
const flags = { [normalizedItem]: true };
|
|
4033
|
+
// Also set singular for create-component which uses 'hook'
|
|
4034
|
+
if (normalizedItem === 'hooks')
|
|
4035
|
+
flags.hook = true;
|
|
4036
|
+
if (section) {
|
|
4037
|
+
console.log(`ℹ Adding ${normalizedItem} to feature: ${section.featurePath}`);
|
|
4038
|
+
return await addSectionCommand(undefined, section.featurePath, { ...options, ...flags });
|
|
4039
|
+
}
|
|
4040
|
+
if (component) {
|
|
4041
|
+
console.log(`ℹ Adding ${normalizedItem} to component: ${component.name}`);
|
|
4042
|
+
// For create-component, we might need to be careful with flags that are on by default
|
|
4043
|
+
// but getEffectiveOptions should handle it if we pass them explicitly as true.
|
|
4044
|
+
return await createComponentCommand(component.name, { ...options, ...flags });
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
catch (error) {
|
|
4048
|
+
console.error('Error:', error.message);
|
|
4049
|
+
if (typeof process.exit === 'function' && process.env.NODE_ENV !== 'test') {
|
|
4050
|
+
process.exit(1);
|
|
4051
|
+
}
|
|
4052
|
+
throw error;
|
|
4053
|
+
}
|
|
4054
|
+
}
|
|
4055
|
+
|
|
4056
|
+
exports.addItemCommand = addItemCommand;
|
|
3797
4057
|
exports.addSectionCommand = addSectionCommand;
|
|
3798
4058
|
exports.adoptCommand = adoptCommand;
|
|
3799
4059
|
exports.createComponentCommand = createComponentCommand;
|
|
@@ -3801,8 +4061,10 @@ exports.initCommand = initCommand;
|
|
|
3801
4061
|
exports.listSectionsCommand = listSectionsCommand;
|
|
3802
4062
|
exports.moveSectionCommand = moveSectionCommand;
|
|
3803
4063
|
exports.normalizeStateCommand = normalizeStateCommand;
|
|
4064
|
+
exports.pruneMissingCommand = pruneMissingCommand;
|
|
3804
4065
|
exports.removeComponentCommand = removeComponentCommand;
|
|
3805
4066
|
exports.removeSectionCommand = removeSectionCommand;
|
|
4067
|
+
exports.renameCommand = renameCommand;
|
|
3806
4068
|
exports.statusCommand = statusCommand;
|
|
3807
4069
|
exports.syncCommand = syncCommand;
|
|
3808
4070
|
exports.upgradeConfigCommand = upgradeConfigCommand;
|