@oamm/textor 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -382,6 +382,8 @@ function normalizeComponentName(name) {
382
382
  return toPascalCase(name);
383
383
  }
384
384
  function normalizeRoute(route) {
385
+ if (!route)
386
+ return null;
385
387
  let normalized = route.trim();
386
388
  if (!normalized.startsWith('/')) {
387
389
  normalized = '/' + normalized;
@@ -1226,8 +1228,13 @@ async function registerFile(filePath, { kind, template, hash, templateVersion =
1226
1228
  }
1227
1229
  async function addSectionToState(section) {
1228
1230
  const state = await loadState();
1229
- // Avoid duplicates by route
1230
- state.sections = state.sections.filter(s => s.route !== section.route);
1231
+ // Avoid duplicates by route OR by featurePath if route is null
1232
+ if (section.route) {
1233
+ state.sections = state.sections.filter(s => s.route !== section.route);
1234
+ }
1235
+ else {
1236
+ state.sections = state.sections.filter(s => s.featurePath !== section.featurePath || s.route);
1237
+ }
1231
1238
  state.sections.push(section);
1232
1239
  await saveState(state);
1233
1240
  }
@@ -1349,6 +1356,12 @@ async function stageFiles(filePaths) {
1349
1356
  async function addSectionCommand(route, featurePath, options) {
1350
1357
  try {
1351
1358
  const config = await loadConfig();
1359
+ // Handle optional route
1360
+ if (typeof featurePath === 'object' || featurePath === undefined) {
1361
+ options = featurePath || options || {};
1362
+ featurePath = route;
1363
+ route = null;
1364
+ }
1352
1365
  const effectiveOptions = getEffectiveOptions(options, config, 'features');
1353
1366
  const normalizedRoute = normalizeRoute(route);
1354
1367
  const normalizedFeaturePath = featureToDirectoryPath(featurePath);
@@ -1359,7 +1372,7 @@ async function addSectionCommand(route, featurePath, options) {
1359
1372
  // Check if we should use nested mode even if config says flat
1360
1373
  // (because the directory already exists, suggesting it should be an index file)
1361
1374
  let effectiveRoutingMode = config.routing.mode;
1362
- if (effectiveRoutingMode === 'flat') {
1375
+ if (normalizedRoute && effectiveRoutingMode === 'flat') {
1363
1376
  const routeDirName = routeToFilePath(normalizedRoute, {
1364
1377
  extension: '',
1365
1378
  mode: 'flat'
@@ -1369,17 +1382,17 @@ async function addSectionCommand(route, featurePath, options) {
1369
1382
  effectiveRoutingMode = 'nested';
1370
1383
  }
1371
1384
  }
1372
- const routeFileName = routeToFilePath(normalizedRoute, {
1385
+ const routeFileName = normalizedRoute ? routeToFilePath(normalizedRoute, {
1373
1386
  extension: routeExtension,
1374
1387
  mode: effectiveRoutingMode,
1375
1388
  indexFile: config.routing.indexFile
1376
- });
1389
+ }) : null;
1377
1390
  const featureComponentName = getFeatureComponentName(normalizedFeaturePath);
1378
1391
  const featureFileName = getFeatureFileName(normalizedFeaturePath, {
1379
1392
  extension: config.naming.featureExtension,
1380
1393
  strategy: effectiveOptions.entry
1381
1394
  });
1382
- const routeFilePath = secureJoin(pagesRoot, routeFileName);
1395
+ const routeFilePath = routeFileName ? secureJoin(pagesRoot, routeFileName) : null;
1383
1396
  const featureDirPath = secureJoin(featuresRoot, normalizedFeaturePath);
1384
1397
  const featureFilePath = secureJoin(featureDirPath, featureFileName);
1385
1398
  const scriptsIndexPath = secureJoin(featureDirPath, config.features.scriptsIndexFile);
@@ -1402,9 +1415,9 @@ async function addSectionCommand(route, featurePath, options) {
1402
1415
  const schemasFilePath = path.join(schemasDirInside, 'index.ts');
1403
1416
  const readmeFilePath = path.join(featureDirPath, 'README.md');
1404
1417
  const storiesFilePath = path.join(featureDirPath, `${featureComponentName}.stories.tsx`);
1405
- const routeParts = normalizedRoute.split('/').filter(Boolean);
1418
+ const routeParts = normalizedRoute ? normalizedRoute.split('/').filter(Boolean) : [];
1406
1419
  const reorganizations = [];
1407
- if (routeParts.length > 1 && config.routing.mode === 'flat') {
1420
+ if (normalizedRoute && routeParts.length > 1 && config.routing.mode === 'flat') {
1408
1421
  const possibleExtensions = ['.astro', '.ts', '.js', '.md', '.mdx', '.html'];
1409
1422
  for (let i = 1; i < routeParts.length; i++) {
1410
1423
  const parentRoute = '/' + routeParts.slice(0, i).join('/');
@@ -1435,7 +1448,8 @@ async function addSectionCommand(route, featurePath, options) {
1435
1448
  }
1436
1449
  if (options.dryRun) {
1437
1450
  console.log('Dry run - would create:');
1438
- console.log(` Route: ${routeFilePath}`);
1451
+ if (routeFilePath)
1452
+ console.log(` Route: ${routeFilePath}`);
1439
1453
  console.log(` Feature: ${featureFilePath}`);
1440
1454
  for (const reorg of reorganizations) {
1441
1455
  console.log(` Reorganize: ${reorg.from} -> ${reorg.to}`);
@@ -1483,7 +1497,8 @@ async function addSectionCommand(route, featurePath, options) {
1483
1497
  }
1484
1498
  await saveState(state);
1485
1499
  }
1486
- await ensureNotExists(routeFilePath, options.force);
1500
+ if (routeFilePath)
1501
+ await ensureNotExists(routeFilePath, options.force);
1487
1502
  await ensureNotExists(featureFilePath, options.force);
1488
1503
  if (shouldCreateIndex)
1489
1504
  await ensureNotExists(indexFilePath, options.force);
@@ -1508,7 +1523,7 @@ async function addSectionCommand(route, featurePath, options) {
1508
1523
  if (shouldCreateScriptsDir)
1509
1524
  await ensureNotExists(scriptsIndexPath, options.force);
1510
1525
  let layoutImportPath = null;
1511
- if (options.layout !== 'none') {
1526
+ if (routeFilePath && options.layout !== 'none') {
1512
1527
  if (config.importAliases.layouts) {
1513
1528
  layoutImportPath = `${config.importAliases.layouts}/${options.layout}.astro`;
1514
1529
  }
@@ -1517,17 +1532,24 @@ async function addSectionCommand(route, featurePath, options) {
1517
1532
  layoutImportPath = getRelativeImportPath(routeFilePath, layoutFilePath);
1518
1533
  }
1519
1534
  }
1520
- let featureImportPath;
1521
- if (config.importAliases.features) {
1522
- const entryPart = effectiveOptions.entry === 'index' ? '' : `/${featureComponentName}`;
1523
- // In Astro, we can often omit the extension for .tsx files, but not for .astro files if using aliases sometimes.
1524
- // However, to be safe, we use the configured extension.
1525
- featureImportPath = `${config.importAliases.features}/${normalizedFeaturePath}${entryPart}${config.naming.featureExtension}`;
1526
- }
1527
- else {
1528
- const relativeFeatureFile = getRelativeImportPath(routeFilePath, featureFilePath);
1529
- // Remove extension for import
1530
- featureImportPath = relativeFeatureFile.replace(/\.[^/.]+$/, '');
1535
+ let featureImportPath = null;
1536
+ if (routeFilePath) {
1537
+ if (config.importAliases.features) {
1538
+ const entryPart = effectiveOptions.entry === 'index' ? '' : `/${featureComponentName}`;
1539
+ // In Astro, we can often omit the extension for .tsx files, but not for .astro files if using aliases sometimes.
1540
+ // However, to be safe, we use the configured extension.
1541
+ featureImportPath = `${config.importAliases.features}/${normalizedFeaturePath}${entryPart}${config.naming.featureExtension}`;
1542
+ }
1543
+ else {
1544
+ const relativeFeatureFile = getRelativeImportPath(routeFilePath, featureFilePath);
1545
+ // Remove extension for import if it's not an .astro file
1546
+ if (config.naming.featureExtension === '.astro') {
1547
+ featureImportPath = relativeFeatureFile;
1548
+ }
1549
+ else {
1550
+ featureImportPath = relativeFeatureFile.replace(/\.[^/.]+$/, '');
1551
+ }
1552
+ }
1531
1553
  }
1532
1554
  let scriptImportPath;
1533
1555
  if (shouldCreateScriptsDir) {
@@ -1535,23 +1557,28 @@ async function addSectionCommand(route, featurePath, options) {
1535
1557
  }
1536
1558
  let routeContent;
1537
1559
  let routeSignature;
1538
- if (options.endpoint) {
1539
- routeContent = generateEndpointTemplate(featureComponentName);
1540
- routeSignature = getSignature(config, 'typescript');
1541
- }
1542
- else {
1543
- routeContent = generateRouteTemplate(options.layout, layoutImportPath, featureImportPath, featureComponentName);
1544
- routeSignature = getSignature(config, 'astro');
1560
+ if (routeFilePath) {
1561
+ if (options.endpoint) {
1562
+ routeContent = generateEndpointTemplate(featureComponentName);
1563
+ routeSignature = getSignature(config, 'typescript');
1564
+ }
1565
+ else {
1566
+ routeContent = generateRouteTemplate(options.layout, layoutImportPath, featureImportPath, featureComponentName);
1567
+ routeSignature = getSignature(config, 'astro');
1568
+ }
1545
1569
  }
1546
1570
  const featureContent = generateFeatureTemplate(featureComponentName, scriptImportPath, framework);
1547
- const routeHash = await writeFileWithSignature(routeFilePath, routeContent, routeSignature, config.hashing?.normalization);
1548
- await registerFile(routeFilePath, {
1549
- kind: 'route',
1550
- template: options.endpoint ? 'endpoint' : 'route',
1551
- hash: routeHash,
1552
- owner: normalizedRoute
1553
- });
1554
- const writtenFiles = [routeFilePath];
1571
+ const writtenFiles = [];
1572
+ if (routeFilePath) {
1573
+ const routeHash = await writeFileWithSignature(routeFilePath, routeContent, routeSignature, config.hashing?.normalization);
1574
+ await registerFile(routeFilePath, {
1575
+ kind: 'route',
1576
+ template: options.endpoint ? 'endpoint' : 'route',
1577
+ hash: routeHash,
1578
+ owner: normalizedRoute
1579
+ });
1580
+ writtenFiles.push(routeFilePath);
1581
+ }
1555
1582
  await ensureDir(featureDirPath);
1556
1583
  if (shouldCreateSubComponentsDir)
1557
1584
  await ensureDir(subComponentsDir);
@@ -1706,7 +1733,8 @@ async function addSectionCommand(route, featurePath, options) {
1706
1733
  await formatFiles(writtenFiles, config.formatting.tool);
1707
1734
  }
1708
1735
  console.log('✓ Section created successfully:');
1709
- console.log(` Route: ${routeFilePath}`);
1736
+ if (routeFilePath)
1737
+ console.log(` Route: ${routeFilePath}`);
1710
1738
  console.log(` Feature: ${featureFilePath}`);
1711
1739
  if (shouldCreateIndex)
1712
1740
  console.log(` Index: ${indexFilePath}`);
@@ -2027,22 +2055,25 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
2027
2055
  if (normalizedFromFeature !== targetFeature) {
2028
2056
  const oldAliasPath = `${config.importAliases.features}/${normalizedFromFeature}`;
2029
2057
  const newAliasPath = `${config.importAliases.features}/${targetFeature}`;
2058
+ const ext = config.naming.featureExtension === '.astro' ? '.astro' : '';
2030
2059
  // Replace both the path and the component name if they are different
2031
- await updateSignature(toRoutePath, `import ${fromFeatureComponentName} from '${oldAliasPath}/${fromFeatureComponentName}'`, `import ${toFeatureComponentName} from '${newAliasPath}/${toFeatureComponentName}'`);
2060
+ await updateSignature(toRoutePath, `import ${fromFeatureComponentName} from '${oldAliasPath}/${fromFeatureComponentName}${ext}'`, `import ${toFeatureComponentName} from '${newAliasPath}/${toFeatureComponentName}${ext}'`);
2032
2061
  // Fallback for prefix only replacement
2033
2062
  await updateSignature(toRoutePath, oldAliasPath, newAliasPath);
2034
2063
  }
2035
2064
  else if (fromFeatureComponentName !== toFeatureComponentName) {
2036
2065
  // Name changed but path didn't
2037
2066
  const aliasPath = `${config.importAliases.features}/${targetFeature}`;
2038
- await updateSignature(toRoutePath, `import ${fromFeatureComponentName} from '${aliasPath}/${fromFeatureComponentName}'`, `import ${toFeatureComponentName} from '${aliasPath}/${toFeatureComponentName}'`);
2067
+ const ext = config.naming.featureExtension === '.astro' ? '.astro' : '';
2068
+ await updateSignature(toRoutePath, `import ${fromFeatureComponentName} from '${aliasPath}/${fromFeatureComponentName}${ext}'`, `import ${toFeatureComponentName} from '${aliasPath}/${toFeatureComponentName}${ext}'`);
2039
2069
  }
2040
2070
  }
2041
2071
  else {
2042
2072
  const oldRelativeDir = getRelativeImportPath(fromRoutePath, fromFeatureDirPath);
2043
2073
  const newRelativeDir = getRelativeImportPath(toRoutePath, toFeatureDirPath);
2044
- const oldImportPath = `import ${fromFeatureComponentName} from '${oldRelativeDir}/${fromFeatureComponentName}'`;
2045
- const newImportPath = `import ${toFeatureComponentName} from '${newRelativeDir}/${toFeatureComponentName}'`;
2074
+ const ext = config.naming.featureExtension === '.astro' ? '.astro' : '';
2075
+ const oldImportPath = `import ${fromFeatureComponentName} from '${oldRelativeDir}/${fromFeatureComponentName}${ext}'`;
2076
+ const newImportPath = `import ${toFeatureComponentName} from '${newRelativeDir}/${toFeatureComponentName}${ext}'`;
2046
2077
  if (oldImportPath !== newImportPath) {
2047
2078
  await updateSignature(toRoutePath, oldImportPath, newImportPath);
2048
2079
  }
@@ -2115,13 +2146,14 @@ async function scanAndReplaceImports(config, state, fromInfo, toInfo, options) {
2115
2146
  continue;
2116
2147
  let content = await readFile(fullPath, 'utf-8');
2117
2148
  let changed = false;
2149
+ const ext = config.naming.featureExtension === '.astro' ? '.astro' : '';
2118
2150
  // Handle Aliases
2119
2151
  if (config.importAliases.features) {
2120
2152
  const oldAlias = `${config.importAliases.features}/${fromFeaturePath}`;
2121
2153
  const newAlias = `${config.importAliases.features}/${toFeaturePath}`;
2122
2154
  // Update component name and path if both changed
2123
- const oldFullImport = `from '${oldAlias}/${fromComponentName}'`;
2124
- const newFullImport = `from '${newAlias}/${toComponentName}'`;
2155
+ const oldFullImport = `from '${oldAlias}/${fromComponentName}${ext}'`;
2156
+ const newFullImport = `from '${newAlias}/${toComponentName}${ext}'`;
2125
2157
  if (content.includes(oldFullImport)) {
2126
2158
  content = content.replace(new RegExp(oldFullImport.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newFullImport);
2127
2159
  changed = true;
@@ -2138,8 +2170,8 @@ async function scanAndReplaceImports(config, state, fromInfo, toInfo, options) {
2138
2170
  const toFeatureDir = secureJoin(featuresRoot, toFeaturePath);
2139
2171
  const oldRelPath = getRelativeImportPath(fullPath, fromFeatureDir);
2140
2172
  const newRelPath = getRelativeImportPath(fullPath, toFeatureDir);
2141
- const oldImport = `'${oldRelPath}/${fromComponentName}'`;
2142
- const newImport = `'${newRelPath}/${toComponentName}'`;
2173
+ const oldImport = `'${oldRelPath}/${fromComponentName}${ext}'`;
2174
+ const newImport = `'${newRelPath}/${toComponentName}${ext}'`;
2143
2175
  if (content.includes(oldImport)) {
2144
2176
  content = content.replace(new RegExp(oldImport.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newImport);
2145
2177
  changed = true;