@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/README.md +28 -1
- package/dist/bin/textor.js +144 -63
- package/dist/bin/textor.js.map +1 -1
- package/dist/index.cjs +122 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +122 -56
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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$
|
|
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$
|
|
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
|
-
//
|
|
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
|
-
|
|
2365
|
+
changed = true;
|
|
2340
2366
|
}
|
|
2341
2367
|
if (config.importAliases.features) {
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
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 (
|
|
2352
|
-
//
|
|
2353
|
-
|
|
2354
|
-
|
|
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(
|
|
2389
|
+
const oldRelativeDir = getRelativeImportPath(toRoutePath, fromFeatureDirPath);
|
|
2360
2390
|
const newRelativeDir = getRelativeImportPath(toRoutePath, toFeatureDirPath);
|
|
2361
|
-
|
|
2362
|
-
const
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
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 {
|