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