@oamm/textor 1.0.7 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -41,6 +41,7 @@ const CURRENT_CONFIG_VERSION = 2;
41
41
  * @property {boolean} features.createScriptsDir
42
42
  * @property {string} features.scriptsIndexFile
43
43
  * @property {string} features.layout
44
+ * @property {Object} features.layoutProps
44
45
  * @property {Object} components
45
46
  * @property {boolean} components.createSubComponentsDir
46
47
  * @property {boolean} components.createContext
@@ -106,7 +107,8 @@ const DEFAULT_CONFIG = {
106
107
  createReadme: false,
107
108
  createStories: false,
108
109
  createIndex: false,
109
- layout: 'Main'
110
+ layout: 'Main',
111
+ layoutProps: {}
110
112
  },
111
113
  components: {
112
114
  framework: 'react',
@@ -876,27 +878,6 @@ async function formatFiles(filePaths, tool) {
876
878
  }
877
879
  }
878
880
 
879
- var filesystem = /*#__PURE__*/Object.freeze({
880
- __proto__: null,
881
- calculateHash: calculateHash,
882
- cleanupEmptyDirs: cleanupEmptyDirs,
883
- ensureDir: ensureDir,
884
- ensureNotExists: ensureNotExists,
885
- formatFiles: formatFiles,
886
- getSignature: getSignature,
887
- inferKind: inferKind,
888
- isEmptyDir: isEmptyDir,
889
- isTextorGenerated: isTextorGenerated,
890
- safeDelete: safeDelete,
891
- safeDeleteDir: safeDeleteDir,
892
- safeMove: safeMove,
893
- scanDirectory: scanDirectory,
894
- secureJoin: secureJoin,
895
- updateSignature: updateSignature,
896
- verifyFileIntegrity: verifyFileIntegrity,
897
- writeFileWithSignature: writeFileWithSignature
898
- });
899
-
900
881
  function renderNamePattern(pattern, data = {}, label = 'pattern') {
901
882
  if (typeof pattern !== 'string')
902
883
  return null;
@@ -958,13 +939,15 @@ function getTemplateOverride(templateName, extension, data = {}) {
958
939
  * - layoutImportPath: Path to import the layout
959
940
  * - featureImportPath: Path to import the feature component
960
941
  * - featureComponentName: Name of the feature component
942
+ * - layoutProps: Optional properties for the layout
961
943
  */
962
- function generateRouteTemplate(layoutName, layoutImportPath, featureImportPath, featureComponentName, extension = '.astro') {
944
+ function generateRouteTemplate(layoutName, layoutImportPath, featureImportPath, featureComponentName, extension = '.astro', layoutProps = {}) {
963
945
  const override = getTemplateOverride('route', extension, {
964
946
  layoutName,
965
947
  layoutImportPath,
966
948
  featureImportPath,
967
- featureComponentName
949
+ featureComponentName,
950
+ ...layoutProps
968
951
  });
969
952
  if (override)
970
953
  return override;
@@ -976,12 +959,24 @@ import ${featureComponentName} from '${featureImportPath}';
976
959
  <${featureComponentName} />
977
960
  `;
978
961
  }
962
+ const propsStr = Object.entries(layoutProps)
963
+ .map(([key, value]) => {
964
+ if (typeof value === 'string' && value.startsWith('{') && value.endsWith('}')) {
965
+ return `${key}=${value}`;
966
+ }
967
+ if (typeof value === 'string') {
968
+ return `${key}="${value}"`;
969
+ }
970
+ return `${key}={${JSON.stringify(value)}}`;
971
+ })
972
+ .join(' ');
973
+ const layoutOpening = propsStr ? `<${layoutName} ${propsStr}>` : `<${layoutName}>`;
979
974
  return `---
980
975
  import ${layoutName} from '${layoutImportPath}';
981
976
  import ${featureComponentName} from '${featureImportPath}';
982
977
  ---
983
978
 
984
- <${layoutName}>
979
+ ${layoutOpening}
985
980
  <${featureComponentName} />
986
981
  </${layoutName}>
987
982
  `;
@@ -1180,6 +1175,10 @@ function generateIndexTemplate(componentName, componentExtension) {
1180
1175
  const override = getTemplateOverride('index', '.ts', { componentName, componentExtension });
1181
1176
  if (override)
1182
1177
  return override;
1178
+ if (componentExtension === '.astro') {
1179
+ return `export * from './types';
1180
+ `;
1181
+ }
1183
1182
  return `export { default as ${componentName} } from './${componentName}${componentExtension}';
1184
1183
  export * from './types';
1185
1184
  `;
@@ -1593,7 +1592,7 @@ async function addSectionCommand(route, featurePath, options) {
1593
1592
  const apiDirInside = secureJoin(featureDirPath, 'api');
1594
1593
  const servicesDirInside = secureJoin(featureDirPath, 'services');
1595
1594
  const schemasDirInside = secureJoin(featureDirPath, 'schemas');
1596
- const { framework, layout, createSubComponentsDir: shouldCreateSubComponentsDir, createScriptsDir: shouldCreateScriptsDir, createApi: shouldCreateApi, createServices: shouldCreateServices, createSchemas: shouldCreateSchemas, createHooks: shouldCreateHooks, createContext: shouldCreateContext, createTests: shouldCreateTests, createTypes: shouldCreateTypes, createReadme: shouldCreateReadme, createStories: shouldCreateStories, createIndex: shouldCreateIndex } = effectiveOptions;
1595
+ const { framework, layout, layoutProps: configLayoutProps, createSubComponentsDir: shouldCreateSubComponentsDir, createScriptsDir: shouldCreateScriptsDir, createApi: shouldCreateApi, createServices: shouldCreateServices, createSchemas: shouldCreateSchemas, createHooks: shouldCreateHooks, createContext: shouldCreateContext, createTests: shouldCreateTests, createTypes: shouldCreateTypes, createReadme: shouldCreateReadme, createStories: shouldCreateStories, createIndex: shouldCreateIndex } = effectiveOptions;
1597
1596
  const featurePatterns = config.filePatterns?.features || {};
1598
1597
  const patternData = {
1599
1598
  componentName: featureComponentName,
@@ -1690,7 +1689,7 @@ async function addSectionCommand(route, featurePath, options) {
1690
1689
  delete state.files[oldRelative];
1691
1690
  }
1692
1691
  // Update imports in the moved file
1693
- await updateImportsInFile$1(reorg.to, reorg.from, reorg.to);
1692
+ await updateImportsInFile$2(reorg.to, reorg.from, reorg.to);
1694
1693
  // Update hash in state after import updates
1695
1694
  if (state.files[newRelative]) {
1696
1695
  const content = await promises.readFile(reorg.to, 'utf-8');
@@ -1726,6 +1725,30 @@ async function addSectionCommand(route, featurePath, options) {
1726
1725
  if (shouldCreateScriptsDir)
1727
1726
  await ensureNotExists(scriptsIndexPath, options.force);
1728
1727
  let layoutImportPath = null;
1728
+ const cliProps = options.prop || {};
1729
+ const rawLayoutProps = { ...configLayoutProps, ...cliProps };
1730
+ const layoutProps = {};
1731
+ // Resolve variables in layoutProps
1732
+ const substitutionData = enrichData({
1733
+ componentName: featureComponentName,
1734
+ layoutName: layout,
1735
+ featureComponentName: featureComponentName
1736
+ });
1737
+ for (const [key, value] of Object.entries(rawLayoutProps)) {
1738
+ if (typeof value === 'string') {
1739
+ let resolvedValue = value;
1740
+ for (const [varKey, varValue] of Object.entries(substitutionData)) {
1741
+ const regex = new RegExp(`{{${varKey}}}`, 'g');
1742
+ resolvedValue = resolvedValue.replace(regex, varValue);
1743
+ const underscoreRegex = new RegExp(`__${varKey}__`, 'g');
1744
+ resolvedValue = resolvedValue.replace(underscoreRegex, varValue);
1745
+ }
1746
+ layoutProps[key] = resolvedValue;
1747
+ }
1748
+ else {
1749
+ layoutProps[key] = value;
1750
+ }
1751
+ }
1729
1752
  if (routeFilePath && layout !== 'none') {
1730
1753
  if (config.importAliases.layouts) {
1731
1754
  layoutImportPath = `${config.importAliases.layouts}/${layout}.astro`;
@@ -1738,7 +1761,7 @@ async function addSectionCommand(route, featurePath, options) {
1738
1761
  let featureImportPath = null;
1739
1762
  if (routeFilePath) {
1740
1763
  if (config.importAliases.features) {
1741
- const entryPart = effectiveOptions.entry === 'index' ? '' : `/${featureComponentName}`;
1764
+ const entryPart = effectiveOptions.entry === 'index' ? '/index' : `/${featureComponentName}`;
1742
1765
  // In Astro, we can often omit the extension for .tsx files, but not for .astro files if using aliases sometimes.
1743
1766
  // However, to be safe, we use the configured extension.
1744
1767
  featureImportPath = `${config.importAliases.features}/${normalizedFeaturePath}${entryPart}${config.naming.featureExtension}`;
@@ -1766,7 +1789,7 @@ async function addSectionCommand(route, featurePath, options) {
1766
1789
  routeSignature = getSignature(config, 'typescript');
1767
1790
  }
1768
1791
  else {
1769
- routeContent = generateRouteTemplate(layout, layoutImportPath, featureImportPath, featureComponentName, routeExtension);
1792
+ routeContent = generateRouteTemplate(layout, layoutImportPath, featureImportPath, featureComponentName, routeExtension, layoutProps);
1770
1793
  routeSignature = getSignature(config, 'astro');
1771
1794
  }
1772
1795
  }
@@ -1982,7 +2005,7 @@ async function addSectionCommand(route, featurePath, options) {
1982
2005
  throw error;
1983
2006
  }
1984
2007
  }
1985
- async function updateImportsInFile$1(filePath, oldFilePath, newFilePath) {
2008
+ async function updateImportsInFile$2(filePath, oldFilePath, newFilePath) {
1986
2009
  if (!fs.existsSync(filePath))
1987
2010
  return;
1988
2011
  let content = await promises.readFile(filePath, 'utf-8');
@@ -2160,7 +2183,7 @@ async function removeSectionCommand(route, featurePath, options) {
2160
2183
  const newRelative = path.relative(process.cwd(), flatFilePath).replace(/\\/g, '/');
2161
2184
  state.files[newRelative] = { ...state.files[oldRelative] };
2162
2185
  delete state.files[oldRelative];
2163
- await updateImportsInFile(flatFilePath, loneFilePath, flatFilePath);
2186
+ await updateImportsInFile$1(flatFilePath, loneFilePath, flatFilePath);
2164
2187
  // Update hash in state after import updates
2165
2188
  const content = await promises.readFile(flatFilePath, 'utf-8');
2166
2189
  state.files[newRelative].hash = calculateHash(content, config.hashing?.normalization);
@@ -2186,7 +2209,7 @@ async function removeSectionCommand(route, featurePath, options) {
2186
2209
  throw error;
2187
2210
  }
2188
2211
  }
2189
- async function updateImportsInFile(filePath, oldFilePath, newFilePath) {
2212
+ async function updateImportsInFile$1(filePath, oldFilePath, newFilePath) {
2190
2213
  if (!fs.existsSync(filePath))
2191
2214
  return;
2192
2215
  let content = await promises.readFile(filePath, 'utf-8');
@@ -2331,40 +2354,58 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
2331
2354
  const toFeatureDirPath = secureJoin(featuresRoot, targetFeature);
2332
2355
  const fromFeatureComponentName = getFeatureComponentName(normalizedFromFeature);
2333
2356
  const toFeatureComponentName = getFeatureComponentName(targetFeature);
2334
- // Update component name in JSX
2357
+ // First, update all relative imports in the file because it moved
2358
+ await updateImportsInFile(toRoutePath, fromRoutePath, toRoutePath);
2359
+ let content = await promises.readFile(toRoutePath, 'utf-8');
2360
+ let changed = false;
2361
+ // Update component name in JSX tags
2335
2362
  if (fromFeatureComponentName !== toFeatureComponentName) {
2336
- let content = await promises.readFile(toRoutePath, 'utf-8');
2337
2363
  content = content.replace(new RegExp(`<${fromFeatureComponentName}`, 'g'), `<${toFeatureComponentName}`);
2338
2364
  content = content.replace(new RegExp(`</${fromFeatureComponentName}`, 'g'), `</${toFeatureComponentName}`);
2339
- await promises.writeFile(toRoutePath, content, 'utf-8');
2365
+ changed = true;
2340
2366
  }
2341
2367
  if (config.importAliases.features) {
2342
- if (normalizedFromFeature !== targetFeature) {
2343
- const oldAliasPath = `${config.importAliases.features}/${normalizedFromFeature}`;
2344
- const newAliasPath = `${config.importAliases.features}/${targetFeature}`;
2345
- const ext = config.naming.featureExtension === '.astro' ? '.astro' : '';
2346
- // Replace both the path and the component name if they are different
2347
- await updateSignature(toRoutePath, `import ${fromFeatureComponentName} from '${oldAliasPath}/${fromFeatureComponentName}${ext}'`, `import ${toFeatureComponentName} from '${newAliasPath}/${toFeatureComponentName}${ext}'`);
2348
- // Fallback for prefix only replacement
2349
- await updateSignature(toRoutePath, oldAliasPath, newAliasPath);
2368
+ const oldAliasPath = `${config.importAliases.features}/${normalizedFromFeature}`;
2369
+ const newAliasPath = `${config.importAliases.features}/${targetFeature}`;
2370
+ // Flexible regex to match import identifier and path with alias
2371
+ const importRegex = new RegExp(`(import\\s+)(${fromFeatureComponentName})(\\s+from\\s+['"])${oldAliasPath}(/[^'"]+)?(['"])`, 'g');
2372
+ if (importRegex.test(content)) {
2373
+ content = content.replace(importRegex, (match, p1, p2, p3, subPath, p5) => {
2374
+ let newSubPath = subPath || '';
2375
+ if (subPath && subPath.includes(fromFeatureComponentName)) {
2376
+ newSubPath = subPath.replace(fromFeatureComponentName, toFeatureComponentName);
2377
+ }
2378
+ return `${p1}${toFeatureComponentName}${p3}${newAliasPath}${newSubPath}${p5}`;
2379
+ });
2380
+ changed = true;
2350
2381
  }
2351
- else if (fromFeatureComponentName !== toFeatureComponentName) {
2352
- // Name changed but path didn't
2353
- const aliasPath = `${config.importAliases.features}/${targetFeature}`;
2354
- const ext = config.naming.featureExtension === '.astro' ? '.astro' : '';
2355
- await updateSignature(toRoutePath, `import ${fromFeatureComponentName} from '${aliasPath}/${fromFeatureComponentName}${ext}'`, `import ${toFeatureComponentName} from '${aliasPath}/${toFeatureComponentName}${ext}'`);
2382
+ else if (content.includes(oldAliasPath)) {
2383
+ // Fallback for path only replacement
2384
+ content = content.replace(new RegExp(oldAliasPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newAliasPath);
2385
+ changed = true;
2356
2386
  }
2357
2387
  }
2358
2388
  else {
2359
- const oldRelativeDir = getRelativeImportPath(fromRoutePath, fromFeatureDirPath);
2389
+ const oldRelativeDir = getRelativeImportPath(toRoutePath, fromFeatureDirPath);
2360
2390
  const newRelativeDir = getRelativeImportPath(toRoutePath, toFeatureDirPath);
2361
- const ext = config.naming.featureExtension === '.astro' ? '.astro' : '';
2362
- const oldImportPath = `import ${fromFeatureComponentName} from '${oldRelativeDir}/${fromFeatureComponentName}${ext}'`;
2363
- const newImportPath = `import ${toFeatureComponentName} from '${newRelativeDir}/${toFeatureComponentName}${ext}'`;
2364
- if (oldImportPath !== newImportPath) {
2365
- await updateSignature(toRoutePath, oldImportPath, newImportPath);
2391
+ // Flexible regex for relative imports
2392
+ const relImportRegex = new RegExp(`(import\\s+)(${fromFeatureComponentName})(\\s+from\\s+['"])${oldRelativeDir}(/[^'"]+)?(['"])`, 'g');
2393
+ if (relImportRegex.test(content)) {
2394
+ content = content.replace(relImportRegex, (match, p1, p2, p3, subPath, p5) => {
2395
+ let newSubPath = subPath || '';
2396
+ if (subPath && subPath.includes(fromFeatureComponentName)) {
2397
+ newSubPath = subPath.replace(fromFeatureComponentName, toFeatureComponentName);
2398
+ }
2399
+ return `${p1}${toFeatureComponentName}${p3}${newRelativeDir}${newSubPath}${p5}`;
2400
+ });
2401
+ changed = true;
2366
2402
  }
2367
2403
  }
2404
+ if (changed) {
2405
+ await promises.writeFile(toRoutePath, content, 'utf-8');
2406
+ // Update hash in state after changes
2407
+ state.files[normalizedToRouteRelative].hash = calculateHash(content, config.hashing?.normalization);
2408
+ }
2368
2409
  }
2369
2410
  if (!isRouteOnly && normalizedFromFeature && normalizedToFeature && normalizedFromFeature !== normalizedToFeature) {
2370
2411
  const fromFeaturePath = secureJoin(featuresRoot, normalizedFromFeature);
@@ -2423,7 +2464,6 @@ async function scanAndReplaceImports(config, state, fromInfo, toInfo, options) {
2423
2464
  const { fromFeaturePath, fromComponentName } = fromInfo;
2424
2465
  const { toFeaturePath, toComponentName } = toInfo;
2425
2466
  const allFiles = new Set();
2426
- const { scanDirectory, calculateHash } = await Promise.resolve().then(function () { return filesystem; });
2427
2467
  await scanDirectory(process.cwd(), allFiles);
2428
2468
  const featuresRoot = resolvePath(config, 'features');
2429
2469
  for (const relPath of allFiles) {
@@ -2538,7 +2578,6 @@ async function moveDirectory(fromPath, toPath, state, config, options = {}) {
2538
2578
  if (hasChanged) {
2539
2579
  await promises.writeFile(toEntryPath, content, 'utf-8');
2540
2580
  // Re-calculate hash after content update
2541
- const { calculateHash } = await Promise.resolve().then(function () { return filesystem; });
2542
2581
  const updatedHash = calculateHash(content, config.hashing?.normalization);
2543
2582
  const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
2544
2583
  if (fileState) {
@@ -2570,6 +2609,33 @@ async function moveDirectory(fromPath, toPath, state, config, options = {}) {
2570
2609
  await promises.rmdir(fromPath);
2571
2610
  }
2572
2611
  }
2612
+ async function updateImportsInFile(filePath, oldFilePath, newFilePath) {
2613
+ if (!fs.existsSync(filePath))
2614
+ return;
2615
+ let content = await promises.readFile(filePath, 'utf-8');
2616
+ const oldDir = path.dirname(oldFilePath);
2617
+ const newDir = path.dirname(newFilePath);
2618
+ if (oldDir === newDir)
2619
+ return;
2620
+ // Find all relative imports
2621
+ const relativeImportRegex = /from\s+['"](\.\.?\/[^'"]+)['"]/g;
2622
+ let match;
2623
+ const replacements = [];
2624
+ while ((match = relativeImportRegex.exec(content)) !== null) {
2625
+ const relativePath = match[1];
2626
+ const absoluteTarget = path.resolve(oldDir, relativePath);
2627
+ const newRelativePath = getRelativeImportPath(newFilePath, absoluteTarget);
2628
+ replacements.push({
2629
+ full: match[0],
2630
+ oldRel: relativePath,
2631
+ newRel: newRelativePath
2632
+ });
2633
+ }
2634
+ for (const repl of replacements) {
2635
+ content = content.replace(repl.full, `from '${repl.newRel}'`);
2636
+ }
2637
+ await promises.writeFile(filePath, content, 'utf-8');
2638
+ }
2573
2639
 
2574
2640
  async function createComponentCommand(componentName, options) {
2575
2641
  try {