@oamm/textor 1.0.9 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/textor.js +134 -27
- package/dist/bin/textor.js.map +1 -1
- package/dist/index.cjs +120 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +120 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/textor.js
CHANGED
|
@@ -1136,6 +1136,68 @@ ${layoutOpening}
|
|
|
1136
1136
|
`;
|
|
1137
1137
|
}
|
|
1138
1138
|
|
|
1139
|
+
function mergeRouteTemplate(existingContent, featureImportPath, featureComponentName, layoutName) {
|
|
1140
|
+
let content = existingContent;
|
|
1141
|
+
|
|
1142
|
+
// 1. Add import
|
|
1143
|
+
const importLine = `import ${featureComponentName} from '${featureImportPath}';`;
|
|
1144
|
+
if (!content.includes(importLine)) {
|
|
1145
|
+
// Find the second --- which marks the end of frontmatter
|
|
1146
|
+
const lines = content.split('\n');
|
|
1147
|
+
let frontMatterEndLine = -1;
|
|
1148
|
+
let dashCount = 0;
|
|
1149
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1150
|
+
if (lines[i].trim() === '---') {
|
|
1151
|
+
dashCount++;
|
|
1152
|
+
if (dashCount === 2) {
|
|
1153
|
+
frontMatterEndLine = i;
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
if (frontMatterEndLine !== -1) {
|
|
1160
|
+
lines.splice(frontMatterEndLine, 0, importLine);
|
|
1161
|
+
content = lines.join('\n');
|
|
1162
|
+
} else if (content.includes('---')) {
|
|
1163
|
+
// If only one --- found, maybe it's just the start?
|
|
1164
|
+
// But standard Astro has two.
|
|
1165
|
+
// Fallback: insert at the beginning if no frontmatter end found
|
|
1166
|
+
content = importLine + '\n' + content;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// 2. Add component usage
|
|
1171
|
+
const componentTag = `<${featureComponentName} />`;
|
|
1172
|
+
if (!content.includes(componentTag)) {
|
|
1173
|
+
if (layoutName && layoutName !== 'none') {
|
|
1174
|
+
const layoutEndTag = `</${layoutName}>`;
|
|
1175
|
+
if (content.includes(layoutEndTag)) {
|
|
1176
|
+
const lines = content.split('\n');
|
|
1177
|
+
let layoutEndLine = -1;
|
|
1178
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1179
|
+
if (lines[i].includes(layoutEndTag)) {
|
|
1180
|
+
layoutEndLine = i;
|
|
1181
|
+
break;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
if (layoutEndLine !== -1) {
|
|
1185
|
+
lines.splice(layoutEndLine, 0, ` ${componentTag}`);
|
|
1186
|
+
content = lines.join('\n');
|
|
1187
|
+
}
|
|
1188
|
+
} else {
|
|
1189
|
+
// Layout might be self-closing or missing end tag?
|
|
1190
|
+
// If it's Textor generated it should have it.
|
|
1191
|
+
content += `\n${componentTag}\n`;
|
|
1192
|
+
}
|
|
1193
|
+
} else {
|
|
1194
|
+
content += `\n${componentTag}\n`;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
return content;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1139
1201
|
/**
|
|
1140
1202
|
* Feature Template Variables:
|
|
1141
1203
|
* - componentName: Name of the feature component
|
|
@@ -1561,9 +1623,9 @@ async function addSectionToState(section) {
|
|
|
1561
1623
|
if (normalizedSection.featurePath) {
|
|
1562
1624
|
normalizedSection.featurePath = normalizeStatePath(normalizedSection.featurePath);
|
|
1563
1625
|
}
|
|
1564
|
-
// Avoid duplicates by route
|
|
1626
|
+
// Avoid duplicates by route AND by featurePath
|
|
1565
1627
|
if (normalizedSection.route) {
|
|
1566
|
-
state.sections = state.sections.filter(s => s.route !== normalizedSection.route);
|
|
1628
|
+
state.sections = state.sections.filter(s => s.route !== normalizedSection.route || s.featurePath !== normalizedSection.featurePath);
|
|
1567
1629
|
} else {
|
|
1568
1630
|
state.sections = state.sections.filter(s => s.featurePath !== normalizedSection.featurePath || s.route);
|
|
1569
1631
|
}
|
|
@@ -1641,6 +1703,10 @@ function reconstructComponents(files, config) {
|
|
|
1641
1703
|
path: componentPath
|
|
1642
1704
|
});
|
|
1643
1705
|
}
|
|
1706
|
+
// Attribute ownership
|
|
1707
|
+
if (!files[filePath].owner) {
|
|
1708
|
+
files[filePath].owner = componentName;
|
|
1709
|
+
}
|
|
1644
1710
|
}
|
|
1645
1711
|
}
|
|
1646
1712
|
}
|
|
@@ -1684,28 +1750,34 @@ function reconstructSections(state, config) {
|
|
|
1684
1750
|
|
|
1685
1751
|
if (!sections.has(finalRoute)) {
|
|
1686
1752
|
// Try to find a matching feature by name
|
|
1687
|
-
const routeName = path.basename(finalRoute === '/' ? 'index' : finalRoute);
|
|
1753
|
+
const routeName = path.basename(finalRoute === '/' ? 'index' : finalRoute).toLowerCase();
|
|
1688
1754
|
// Look for a directory in features with same name or similar
|
|
1689
1755
|
const possibleFeaturePath = Object.keys(files).find(f => {
|
|
1690
1756
|
const nf = f.replace(/\\/g, '/');
|
|
1691
|
-
|
|
1757
|
+
if (!nf.startsWith(featuresRoot + '/')) return false;
|
|
1758
|
+
const relToFeatures = nf.slice(featuresRoot.length + 1);
|
|
1759
|
+
const segments = relToFeatures.toLowerCase().split('/');
|
|
1760
|
+
return segments.includes(routeName);
|
|
1692
1761
|
});
|
|
1693
1762
|
|
|
1694
1763
|
if (possibleFeaturePath) {
|
|
1695
|
-
const
|
|
1696
|
-
const
|
|
1697
|
-
const
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1764
|
+
const relToFeatures = path.dirname(path.relative(featuresRoot, possibleFeaturePath)).replace(/\\/g, '/');
|
|
1765
|
+
const featurePath = relToFeatures === '.' ? featuresRoot : `${featuresRoot}/${relToFeatures}`;
|
|
1766
|
+
const featureName = path.basename(featurePath);
|
|
1767
|
+
|
|
1768
|
+
sections.set(finalRoute, {
|
|
1769
|
+
name: featureName,
|
|
1770
|
+
route: finalRoute,
|
|
1771
|
+
featurePath: featurePath,
|
|
1772
|
+
extension: path.extname(filePath)
|
|
1773
|
+
});
|
|
1774
|
+
|
|
1775
|
+
// Attribute ownership to discovered files
|
|
1776
|
+
if (!files[filePath].owner) files[filePath].owner = finalRoute;
|
|
1777
|
+
for (const f in files) {
|
|
1778
|
+
if (f.startsWith(featurePath + '/') || f === featurePath) {
|
|
1779
|
+
if (!files[f].owner) files[f].owner = finalRoute;
|
|
1780
|
+
}
|
|
1709
1781
|
}
|
|
1710
1782
|
}
|
|
1711
1783
|
}
|
|
@@ -1986,7 +2058,15 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1986
2058
|
await saveState(state);
|
|
1987
2059
|
}
|
|
1988
2060
|
|
|
1989
|
-
if (routeFilePath)
|
|
2061
|
+
if (routeFilePath) {
|
|
2062
|
+
if (existsSync(routeFilePath)) {
|
|
2063
|
+
const isGenerated = await isTextorGenerated(routeFilePath);
|
|
2064
|
+
if (!isGenerated && !options.force) {
|
|
2065
|
+
throw new Error(`File already exists: ${routeFilePath}\nUse --force to overwrite.`);
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
|
|
1990
2070
|
await ensureNotExists(featureFilePath, options.force);
|
|
1991
2071
|
|
|
1992
2072
|
if (shouldCreateIndex) await ensureNotExists(indexFilePath, options.force);
|
|
@@ -2068,15 +2148,42 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
2068
2148
|
routeContent = generateEndpointTemplate(featureComponentName);
|
|
2069
2149
|
routeSignature = getSignature(config, 'typescript');
|
|
2070
2150
|
} else {
|
|
2071
|
-
routeContent = generateRouteTemplate(
|
|
2072
|
-
layout,
|
|
2073
|
-
layoutImportPath,
|
|
2074
|
-
featureImportPath,
|
|
2075
|
-
featureComponentName,
|
|
2076
|
-
routeExtension,
|
|
2077
|
-
layoutProps
|
|
2078
|
-
);
|
|
2079
2151
|
routeSignature = getSignature(config, 'astro');
|
|
2152
|
+
|
|
2153
|
+
if (existsSync(routeFilePath)) {
|
|
2154
|
+
const existingContent = await readFile(routeFilePath, 'utf-8');
|
|
2155
|
+
// Strip existing signature if present
|
|
2156
|
+
let contentToMerge = existingContent;
|
|
2157
|
+
if (existingContent.startsWith(routeSignature)) {
|
|
2158
|
+
contentToMerge = existingContent.slice(routeSignature.length).trimStart();
|
|
2159
|
+
} else {
|
|
2160
|
+
// Check for generic signature if specific one doesn't match
|
|
2161
|
+
const genericSignature = '@generated by Textor';
|
|
2162
|
+
if (existingContent.includes(genericSignature)) {
|
|
2163
|
+
const lines = existingContent.split('\n');
|
|
2164
|
+
if (lines[0].includes(genericSignature)) {
|
|
2165
|
+
lines.shift();
|
|
2166
|
+
contentToMerge = lines.join('\n').trimStart();
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
routeContent = mergeRouteTemplate(
|
|
2172
|
+
contentToMerge,
|
|
2173
|
+
featureImportPath,
|
|
2174
|
+
featureComponentName,
|
|
2175
|
+
layout
|
|
2176
|
+
);
|
|
2177
|
+
} else {
|
|
2178
|
+
routeContent = generateRouteTemplate(
|
|
2179
|
+
layout,
|
|
2180
|
+
layoutImportPath,
|
|
2181
|
+
featureImportPath,
|
|
2182
|
+
featureComponentName,
|
|
2183
|
+
routeExtension,
|
|
2184
|
+
layoutProps
|
|
2185
|
+
);
|
|
2186
|
+
}
|
|
2080
2187
|
}
|
|
2081
2188
|
}
|
|
2082
2189
|
|
package/dist/bin/textor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textor.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"textor.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/index.cjs
CHANGED
|
@@ -981,6 +981,66 @@ ${layoutOpening}
|
|
|
981
981
|
</${layoutName}>
|
|
982
982
|
`;
|
|
983
983
|
}
|
|
984
|
+
function mergeRouteTemplate(existingContent, featureImportPath, featureComponentName, layoutName) {
|
|
985
|
+
let content = existingContent;
|
|
986
|
+
// 1. Add import
|
|
987
|
+
const importLine = `import ${featureComponentName} from '${featureImportPath}';`;
|
|
988
|
+
if (!content.includes(importLine)) {
|
|
989
|
+
// Find the second --- which marks the end of frontmatter
|
|
990
|
+
const lines = content.split('\n');
|
|
991
|
+
let frontMatterEndLine = -1;
|
|
992
|
+
let dashCount = 0;
|
|
993
|
+
for (let i = 0; i < lines.length; i++) {
|
|
994
|
+
if (lines[i].trim() === '---') {
|
|
995
|
+
dashCount++;
|
|
996
|
+
if (dashCount === 2) {
|
|
997
|
+
frontMatterEndLine = i;
|
|
998
|
+
break;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
if (frontMatterEndLine !== -1) {
|
|
1003
|
+
lines.splice(frontMatterEndLine, 0, importLine);
|
|
1004
|
+
content = lines.join('\n');
|
|
1005
|
+
}
|
|
1006
|
+
else if (content.includes('---')) {
|
|
1007
|
+
// If only one --- found, maybe it's just the start?
|
|
1008
|
+
// But standard Astro has two.
|
|
1009
|
+
// Fallback: insert at the beginning if no frontmatter end found
|
|
1010
|
+
content = importLine + '\n' + content;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
// 2. Add component usage
|
|
1014
|
+
const componentTag = `<${featureComponentName} />`;
|
|
1015
|
+
if (!content.includes(componentTag)) {
|
|
1016
|
+
if (layoutName && layoutName !== 'none') {
|
|
1017
|
+
const layoutEndTag = `</${layoutName}>`;
|
|
1018
|
+
if (content.includes(layoutEndTag)) {
|
|
1019
|
+
const lines = content.split('\n');
|
|
1020
|
+
let layoutEndLine = -1;
|
|
1021
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1022
|
+
if (lines[i].includes(layoutEndTag)) {
|
|
1023
|
+
layoutEndLine = i;
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
if (layoutEndLine !== -1) {
|
|
1028
|
+
lines.splice(layoutEndLine, 0, ` ${componentTag}`);
|
|
1029
|
+
content = lines.join('\n');
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
else {
|
|
1033
|
+
// Layout might be self-closing or missing end tag?
|
|
1034
|
+
// If it's Textor generated it should have it.
|
|
1035
|
+
content += `\n${componentTag}\n`;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
else {
|
|
1039
|
+
content += `\n${componentTag}\n`;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
return content;
|
|
1043
|
+
}
|
|
984
1044
|
/**
|
|
985
1045
|
* Feature Template Variables:
|
|
986
1046
|
* - componentName: Name of the feature component
|
|
@@ -1376,9 +1436,9 @@ async function addSectionToState(section) {
|
|
|
1376
1436
|
if (normalizedSection.featurePath) {
|
|
1377
1437
|
normalizedSection.featurePath = normalizeStatePath(normalizedSection.featurePath);
|
|
1378
1438
|
}
|
|
1379
|
-
// Avoid duplicates by route
|
|
1439
|
+
// Avoid duplicates by route AND by featurePath
|
|
1380
1440
|
if (normalizedSection.route) {
|
|
1381
|
-
state.sections = state.sections.filter(s => s.route !== normalizedSection.route);
|
|
1441
|
+
state.sections = state.sections.filter(s => s.route !== normalizedSection.route || s.featurePath !== normalizedSection.featurePath);
|
|
1382
1442
|
}
|
|
1383
1443
|
else {
|
|
1384
1444
|
state.sections = state.sections.filter(s => s.featurePath !== normalizedSection.featurePath || s.route);
|
|
@@ -1458,6 +1518,10 @@ function reconstructComponents(files, config) {
|
|
|
1458
1518
|
path: componentPath
|
|
1459
1519
|
});
|
|
1460
1520
|
}
|
|
1521
|
+
// Attribute ownership
|
|
1522
|
+
if (!files[filePath].owner) {
|
|
1523
|
+
files[filePath].owner = componentName;
|
|
1524
|
+
}
|
|
1461
1525
|
}
|
|
1462
1526
|
}
|
|
1463
1527
|
}
|
|
@@ -1491,25 +1555,34 @@ function reconstructSections(state, config) {
|
|
|
1491
1555
|
const finalRoute = route === '' ? '/' : route;
|
|
1492
1556
|
if (!sections.has(finalRoute)) {
|
|
1493
1557
|
// Try to find a matching feature by name
|
|
1494
|
-
const routeName = path.basename(finalRoute === '/' ? 'index' : finalRoute);
|
|
1558
|
+
const routeName = path.basename(finalRoute === '/' ? 'index' : finalRoute).toLowerCase();
|
|
1495
1559
|
// Look for a directory in features with same name or similar
|
|
1496
1560
|
const possibleFeaturePath = Object.keys(files).find(f => {
|
|
1497
1561
|
const nf = f.replace(/\\/g, '/');
|
|
1498
|
-
|
|
1562
|
+
if (!nf.startsWith(featuresRoot + '/'))
|
|
1563
|
+
return false;
|
|
1564
|
+
const relToFeatures = nf.slice(featuresRoot.length + 1);
|
|
1565
|
+
const segments = relToFeatures.toLowerCase().split('/');
|
|
1566
|
+
return segments.includes(routeName);
|
|
1499
1567
|
});
|
|
1500
1568
|
if (possibleFeaturePath) {
|
|
1501
|
-
const
|
|
1502
|
-
const
|
|
1503
|
-
const
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1569
|
+
const relToFeatures = path.dirname(path.relative(featuresRoot, possibleFeaturePath)).replace(/\\/g, '/');
|
|
1570
|
+
const featurePath = relToFeatures === '.' ? featuresRoot : `${featuresRoot}/${relToFeatures}`;
|
|
1571
|
+
const featureName = path.basename(featurePath);
|
|
1572
|
+
sections.set(finalRoute, {
|
|
1573
|
+
name: featureName,
|
|
1574
|
+
route: finalRoute,
|
|
1575
|
+
featurePath: featurePath,
|
|
1576
|
+
extension: path.extname(filePath)
|
|
1577
|
+
});
|
|
1578
|
+
// Attribute ownership to discovered files
|
|
1579
|
+
if (!files[filePath].owner)
|
|
1580
|
+
files[filePath].owner = finalRoute;
|
|
1581
|
+
for (const f in files) {
|
|
1582
|
+
if (f.startsWith(featurePath + '/') || f === featurePath) {
|
|
1583
|
+
if (!files[f].owner)
|
|
1584
|
+
files[f].owner = finalRoute;
|
|
1585
|
+
}
|
|
1513
1586
|
}
|
|
1514
1587
|
}
|
|
1515
1588
|
}
|
|
@@ -1699,8 +1772,14 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1699
1772
|
}
|
|
1700
1773
|
await saveState(state);
|
|
1701
1774
|
}
|
|
1702
|
-
if (routeFilePath)
|
|
1703
|
-
|
|
1775
|
+
if (routeFilePath) {
|
|
1776
|
+
if (fs.existsSync(routeFilePath)) {
|
|
1777
|
+
const isGenerated = await isTextorGenerated(routeFilePath);
|
|
1778
|
+
if (!isGenerated && !options.force) {
|
|
1779
|
+
throw new Error(`File already exists: ${routeFilePath}\nUse --force to overwrite.`);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1704
1783
|
await ensureNotExists(featureFilePath, options.force);
|
|
1705
1784
|
if (shouldCreateIndex)
|
|
1706
1785
|
await ensureNotExists(indexFilePath, options.force);
|
|
@@ -1789,8 +1868,30 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1789
1868
|
routeSignature = getSignature(config, 'typescript');
|
|
1790
1869
|
}
|
|
1791
1870
|
else {
|
|
1792
|
-
routeContent = generateRouteTemplate(layout, layoutImportPath, featureImportPath, featureComponentName, routeExtension, layoutProps);
|
|
1793
1871
|
routeSignature = getSignature(config, 'astro');
|
|
1872
|
+
if (fs.existsSync(routeFilePath)) {
|
|
1873
|
+
const existingContent = await promises.readFile(routeFilePath, 'utf-8');
|
|
1874
|
+
// Strip existing signature if present
|
|
1875
|
+
let contentToMerge = existingContent;
|
|
1876
|
+
if (existingContent.startsWith(routeSignature)) {
|
|
1877
|
+
contentToMerge = existingContent.slice(routeSignature.length).trimStart();
|
|
1878
|
+
}
|
|
1879
|
+
else {
|
|
1880
|
+
// Check for generic signature if specific one doesn't match
|
|
1881
|
+
const genericSignature = '@generated by Textor';
|
|
1882
|
+
if (existingContent.includes(genericSignature)) {
|
|
1883
|
+
const lines = existingContent.split('\n');
|
|
1884
|
+
if (lines[0].includes(genericSignature)) {
|
|
1885
|
+
lines.shift();
|
|
1886
|
+
contentToMerge = lines.join('\n').trimStart();
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
routeContent = mergeRouteTemplate(contentToMerge, featureImportPath, featureComponentName, layout);
|
|
1891
|
+
}
|
|
1892
|
+
else {
|
|
1893
|
+
routeContent = generateRouteTemplate(layout, layoutImportPath, featureImportPath, featureComponentName, routeExtension, layoutProps);
|
|
1894
|
+
}
|
|
1794
1895
|
}
|
|
1795
1896
|
}
|
|
1796
1897
|
const featureContent = generateFeatureTemplate(featureComponentName, scriptImportPath, framework, config.naming.featureExtension);
|