@oamm/textor 1.0.4 → 1.0.5
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 +25 -2
- package/dist/bin/textor.js +67 -21
- package/dist/bin/textor.js.map +1 -1
- package/dist/index.cjs +59 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +59 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -466,7 +466,7 @@ You can customize the code generated by Textor by providing your own templates.
|
|
|
466
466
|
|
|
467
467
|
1. Create the `.textor/templates/` directory if it doesn't exist.
|
|
468
468
|
2. Create a file named according to the table below (e.g., `feature.astro` or `component.tsx`).
|
|
469
|
-
3. Use `{{variable}}` placeholders in your template. Textor will automatically replace them when generating files.
|
|
469
|
+
3. Use `{{variable}}` or `__variable__` placeholders in your template. Textor will automatically replace them when generating files. Using `__variable__` (e.g., `__componentName__`) is recommended for TypeScript/JavaScript templates as it is a valid identifier and avoids "broken code" warnings in your IDE.
|
|
470
470
|
|
|
471
471
|
### Supported Templates
|
|
472
472
|
|
|
@@ -495,15 +495,38 @@ You can customize the code generated by Textor by providing your own templates.
|
|
|
495
495
|
### Variables Description
|
|
496
496
|
|
|
497
497
|
- `{{componentName}}`: The PascalCase name of the feature or component (e.g., `UserCatalog`).
|
|
498
|
+
- `{{componentNameCamel}}`: camelCase version of the name (e.g., `userCatalog`).
|
|
499
|
+
- `{{componentNameKebab}}`: kebab-case version of the name (e.g., `user-catalog`).
|
|
500
|
+
- `{{componentNameSnake}}`: snake_case version of the name (e.g., `user_catalog`).
|
|
501
|
+
- `{{componentNameUpper}}`: SCREAMING_SNAKE_CASE version of the name (e.g., `USER_CATALOG`).
|
|
498
502
|
- `{{hookName}}`: The camelCase name of the generated hook (e.g., `useUserCatalog`).
|
|
499
503
|
- `{{componentPath}}`: Relative path to the component file (useful for imports in tests or stories).
|
|
500
504
|
- `{{featureComponentName}}`: The name of the feature component as imported in a route.
|
|
501
|
-
- `{{
|
|
505
|
+
- `{{featureComponentNameCamel}}`, `{{featureComponentNameKebab}}`, `{{featureComponentNameSnake}}`, `{{featureComponentNameUpper}}`, `{{featureComponentNamePascal}}`: Case variations for the feature component name.
|
|
502
506
|
- `{{layoutName}}`: The name of the layout component being used.
|
|
507
|
+
- `{{layoutNameCamel}}`, `{{layoutNameKebab}}`, `{{layoutNameSnake}}`, `{{layoutNameUpper}}`, `{{layoutNamePascal}}`: Case variations for the layout name.
|
|
503
508
|
- `{{layoutImportPath}}`: The import path for the layout component.
|
|
504
509
|
- `{{scriptImportPath}}`: Relative path to the client-side script entry point.
|
|
505
510
|
- `{{componentExtension}}`: The file extension of the component (e.g., `.astro` or `.tsx`).
|
|
506
511
|
|
|
512
|
+
### Example: Custom Route Template (`.textor/templates/route.ts`)
|
|
513
|
+
|
|
514
|
+
If you are using a custom routing library and want your templates to be valid TypeScript:
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
/**
|
|
518
|
+
* @generated by Textor
|
|
519
|
+
* Route: {{featureComponentName}}
|
|
520
|
+
*/
|
|
521
|
+
import { defineRoute } from "my-router";
|
|
522
|
+
import __featureComponentName__ from "{{featureImportPath}}";
|
|
523
|
+
|
|
524
|
+
export const __featureComponentName__Route = defineRoute({
|
|
525
|
+
path: "/__featureComponentNameKebab__",
|
|
526
|
+
component: __featureComponentName__
|
|
527
|
+
});
|
|
528
|
+
```
|
|
529
|
+
|
|
507
530
|
### Example: Custom Feature Template (`.textor/templates/feature.astro`)
|
|
508
531
|
|
|
509
532
|
```astro
|
package/dist/bin/textor.js
CHANGED
|
@@ -454,12 +454,15 @@ function getEffectiveOptions(cmdOptions, config, type) {
|
|
|
454
454
|
async function initCommand(options) {
|
|
455
455
|
try {
|
|
456
456
|
const configPath = await saveConfig(DEFAULT_CONFIG, options.force);
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
457
|
+
const quiet = options?.quiet || process.env.NODE_ENV === 'test' || process.env.TEXTOR_QUIET === '1';
|
|
458
|
+
|
|
459
|
+
if (!quiet) {
|
|
460
|
+
console.log('Textor configuration created at:', configPath);
|
|
461
|
+
console.log('\nDefault configuration:');
|
|
462
|
+
console.log(JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
463
|
+
console.log('\nYou can now use Textor commands like:');
|
|
464
|
+
console.log(' textor add-section /users users/catalog --layout Main');
|
|
465
|
+
}
|
|
463
466
|
} catch (error) {
|
|
464
467
|
console.error('Error:', error.message);
|
|
465
468
|
process.exit(1);
|
|
@@ -479,6 +482,28 @@ function toPascalCase(input) {
|
|
|
479
482
|
.join('');
|
|
480
483
|
}
|
|
481
484
|
|
|
485
|
+
function toCamelCase(input) {
|
|
486
|
+
const pascal = toPascalCase(input);
|
|
487
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function toKebabCase(input) {
|
|
491
|
+
return input
|
|
492
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
493
|
+
.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
|
|
494
|
+
.replace(/[_\s/\\-]+/g, '-')
|
|
495
|
+
.toLowerCase()
|
|
496
|
+
.replace(/^-+|-+$/g, '');
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function toSnakeCase(input) {
|
|
500
|
+
return toKebabCase(input).replace(/-/g, '_');
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function toScreamingSnakeCase(input) {
|
|
504
|
+
return toSnakeCase(input).toUpperCase();
|
|
505
|
+
}
|
|
506
|
+
|
|
482
507
|
function getFeatureComponentName(featurePath) {
|
|
483
508
|
return toPascalCase(featurePath);
|
|
484
509
|
}
|
|
@@ -1044,12 +1069,30 @@ function resolvePatternedPath(baseDir, pattern, data, fallback, label) {
|
|
|
1044
1069
|
return secureJoin(baseDir, fileName);
|
|
1045
1070
|
}
|
|
1046
1071
|
|
|
1072
|
+
function enrichData(data) {
|
|
1073
|
+
const enriched = { ...data };
|
|
1074
|
+
const nameKeys = ['componentName', 'featureComponentName', 'layoutName'];
|
|
1075
|
+
for (const key of nameKeys) {
|
|
1076
|
+
if (data[key] && typeof data[key] === 'string') {
|
|
1077
|
+
enriched[`${key}Camel`] = toCamelCase(data[key]);
|
|
1078
|
+
enriched[`${key}Kebab`] = toKebabCase(data[key]);
|
|
1079
|
+
enriched[`${key}Snake`] = toSnakeCase(data[key]);
|
|
1080
|
+
enriched[`${key}Upper`] = toScreamingSnakeCase(data[key]);
|
|
1081
|
+
enriched[`${key}Pascal`] = toPascalCase(data[key]);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return enriched;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1047
1087
|
function getTemplateOverride(templateName, extension, data = {}) {
|
|
1048
1088
|
const overridePath = path.join(process.cwd(), '.textor', 'templates', `${templateName}${extension}`);
|
|
1049
1089
|
if (existsSync(overridePath)) {
|
|
1050
1090
|
let content = readFileSync(overridePath, 'utf-8');
|
|
1051
|
-
|
|
1052
|
-
|
|
1091
|
+
const finalData = enrichData(data);
|
|
1092
|
+
for (const [key, value] of Object.entries(finalData)) {
|
|
1093
|
+
const replacement = () => value || '';
|
|
1094
|
+
content = content.replace(new RegExp(`{{${key}}}`, 'g'), replacement);
|
|
1095
|
+
content = content.replace(new RegExp(`__${key}__`, 'g'), replacement);
|
|
1053
1096
|
}
|
|
1054
1097
|
return content;
|
|
1055
1098
|
}
|
|
@@ -1063,8 +1106,8 @@ function getTemplateOverride(templateName, extension, data = {}) {
|
|
|
1063
1106
|
* - featureImportPath: Path to import the feature component
|
|
1064
1107
|
* - featureComponentName: Name of the feature component
|
|
1065
1108
|
*/
|
|
1066
|
-
function generateRouteTemplate(layoutName, layoutImportPath, featureImportPath, featureComponentName) {
|
|
1067
|
-
const override = getTemplateOverride('route',
|
|
1109
|
+
function generateRouteTemplate(layoutName, layoutImportPath, featureImportPath, featureComponentName, extension = '.astro') {
|
|
1110
|
+
const override = getTemplateOverride('route', extension, {
|
|
1068
1111
|
layoutName,
|
|
1069
1112
|
layoutImportPath,
|
|
1070
1113
|
featureImportPath,
|
|
@@ -1097,9 +1140,9 @@ import ${featureComponentName} from '${featureImportPath}';
|
|
|
1097
1140
|
* - componentName: Name of the feature component
|
|
1098
1141
|
* - scriptImportPath: Path to the feature's client-side script
|
|
1099
1142
|
*/
|
|
1100
|
-
function generateFeatureTemplate(componentName, scriptImportPath, framework = 'astro') {
|
|
1101
|
-
const
|
|
1102
|
-
const override = getTemplateOverride('feature',
|
|
1143
|
+
function generateFeatureTemplate(componentName, scriptImportPath, framework = 'astro', extension) {
|
|
1144
|
+
const templateExtension = extension || (framework === 'astro' ? '.astro' : '.tsx');
|
|
1145
|
+
const override = getTemplateOverride('feature', templateExtension, { componentName, scriptImportPath });
|
|
1103
1146
|
if (override) return override;
|
|
1104
1147
|
|
|
1105
1148
|
if (framework === 'react') {
|
|
@@ -1144,9 +1187,9 @@ function generateScriptsIndexTemplate() {
|
|
|
1144
1187
|
* Component Template Variables:
|
|
1145
1188
|
* - componentName: Name of the component
|
|
1146
1189
|
*/
|
|
1147
|
-
function generateComponentTemplate(componentName, framework = 'react') {
|
|
1148
|
-
const
|
|
1149
|
-
const override = getTemplateOverride('component',
|
|
1190
|
+
function generateComponentTemplate(componentName, framework = 'react', extension) {
|
|
1191
|
+
const templateExtension = extension || (framework === 'astro' ? '.astro' : '.tsx');
|
|
1192
|
+
const override = getTemplateOverride('component', templateExtension, { componentName });
|
|
1150
1193
|
if (override) return override;
|
|
1151
1194
|
|
|
1152
1195
|
if (framework === 'react') {
|
|
@@ -1989,13 +2032,14 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1989
2032
|
options.layout,
|
|
1990
2033
|
layoutImportPath,
|
|
1991
2034
|
featureImportPath,
|
|
1992
|
-
featureComponentName
|
|
2035
|
+
featureComponentName,
|
|
2036
|
+
routeExtension
|
|
1993
2037
|
);
|
|
1994
2038
|
routeSignature = getSignature(config, 'astro');
|
|
1995
2039
|
}
|
|
1996
2040
|
}
|
|
1997
2041
|
|
|
1998
|
-
const featureContent = generateFeatureTemplate(featureComponentName, scriptImportPath, framework);
|
|
2042
|
+
const featureContent = generateFeatureTemplate(featureComponentName, scriptImportPath, framework, config.naming.featureExtension);
|
|
1999
2043
|
|
|
2000
2044
|
const writtenFiles = [];
|
|
2001
2045
|
|
|
@@ -3060,7 +3104,7 @@ async function createComponentCommand(componentName, options) {
|
|
|
3060
3104
|
if (shouldCreateServices) await ensureDir(servicesDirInside);
|
|
3061
3105
|
if (shouldCreateSchemas) await ensureDir(schemasDirInside);
|
|
3062
3106
|
|
|
3063
|
-
const componentContent = generateComponentTemplate(normalizedName, framework);
|
|
3107
|
+
const componentContent = generateComponentTemplate(normalizedName, framework, config.naming.componentExtension);
|
|
3064
3108
|
const signature = getSignature(config, config.naming.componentExtension === '.astro' ? 'astro' : 'tsx');
|
|
3065
3109
|
|
|
3066
3110
|
const componentHash = await writeFileWithSignature(
|
|
@@ -3363,13 +3407,14 @@ async function removeComponentCommand(identifier, options) {
|
|
|
3363
3407
|
await cleanupEmptyDirs(path.dirname(componentDir), path.join(process.cwd(), config.paths.components));
|
|
3364
3408
|
|
|
3365
3409
|
// Unregister files
|
|
3366
|
-
const
|
|
3410
|
+
const relComponentPath = path.relative(process.cwd(), componentDir).replace(/\\/g, '/');
|
|
3411
|
+
const dirPrefix = relComponentPath + '/';
|
|
3367
3412
|
for (const f in state.files) {
|
|
3368
3413
|
if (f.startsWith(dirPrefix)) {
|
|
3369
3414
|
delete state.files[f];
|
|
3370
3415
|
}
|
|
3371
3416
|
}
|
|
3372
|
-
state.components = state.components.filter(c => c.name !== identifier && c.path !==
|
|
3417
|
+
state.components = state.components.filter(c => c.name !== identifier && c.path !== relComponentPath);
|
|
3373
3418
|
await saveState(state);
|
|
3374
3419
|
} else if (result.message) {
|
|
3375
3420
|
console.log(`⚠ Skipped: ${componentDir}`);
|
|
@@ -4103,6 +4148,7 @@ program
|
|
|
4103
4148
|
.command('init')
|
|
4104
4149
|
.description('Initialize Textor configuration')
|
|
4105
4150
|
.option('--force', 'Overwrite existing configuration')
|
|
4151
|
+
.option('--quiet', 'Skip printing full default configuration')
|
|
4106
4152
|
.action(initCommand);
|
|
4107
4153
|
|
|
4108
4154
|
program
|
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
|
@@ -421,11 +421,14 @@ function getEffectiveOptions(cmdOptions, config, type) {
|
|
|
421
421
|
async function initCommand(options) {
|
|
422
422
|
try {
|
|
423
423
|
const configPath = await saveConfig(DEFAULT_CONFIG, options.force);
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
424
|
+
const quiet = options?.quiet || process.env.NODE_ENV === 'test' || process.env.TEXTOR_QUIET === '1';
|
|
425
|
+
if (!quiet) {
|
|
426
|
+
console.log('Textor configuration created at:', configPath);
|
|
427
|
+
console.log('\nDefault configuration:');
|
|
428
|
+
console.log(JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
429
|
+
console.log('\nYou can now use Textor commands like:');
|
|
430
|
+
console.log(' textor add-section /users users/catalog --layout Main');
|
|
431
|
+
}
|
|
429
432
|
}
|
|
430
433
|
catch (error) {
|
|
431
434
|
console.error('Error:', error.message);
|
|
@@ -445,6 +448,24 @@ function toPascalCase(input) {
|
|
|
445
448
|
})
|
|
446
449
|
.join('');
|
|
447
450
|
}
|
|
451
|
+
function toCamelCase(input) {
|
|
452
|
+
const pascal = toPascalCase(input);
|
|
453
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
454
|
+
}
|
|
455
|
+
function toKebabCase(input) {
|
|
456
|
+
return input
|
|
457
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
458
|
+
.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
|
|
459
|
+
.replace(/[_\s/\\-]+/g, '-')
|
|
460
|
+
.toLowerCase()
|
|
461
|
+
.replace(/^-+|-+$/g, '');
|
|
462
|
+
}
|
|
463
|
+
function toSnakeCase(input) {
|
|
464
|
+
return toKebabCase(input).replace(/-/g, '_');
|
|
465
|
+
}
|
|
466
|
+
function toScreamingSnakeCase(input) {
|
|
467
|
+
return toSnakeCase(input).toUpperCase();
|
|
468
|
+
}
|
|
448
469
|
function getFeatureComponentName(featurePath) {
|
|
449
470
|
return toPascalCase(featurePath);
|
|
450
471
|
}
|
|
@@ -900,12 +921,29 @@ function resolvePatternedPath(baseDir, pattern, data, fallback, label) {
|
|
|
900
921
|
return secureJoin(baseDir, fileName);
|
|
901
922
|
}
|
|
902
923
|
|
|
924
|
+
function enrichData(data) {
|
|
925
|
+
const enriched = { ...data };
|
|
926
|
+
const nameKeys = ['componentName', 'featureComponentName', 'layoutName'];
|
|
927
|
+
for (const key of nameKeys) {
|
|
928
|
+
if (data[key] && typeof data[key] === 'string') {
|
|
929
|
+
enriched[`${key}Camel`] = toCamelCase(data[key]);
|
|
930
|
+
enriched[`${key}Kebab`] = toKebabCase(data[key]);
|
|
931
|
+
enriched[`${key}Snake`] = toSnakeCase(data[key]);
|
|
932
|
+
enriched[`${key}Upper`] = toScreamingSnakeCase(data[key]);
|
|
933
|
+
enriched[`${key}Pascal`] = toPascalCase(data[key]);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
return enriched;
|
|
937
|
+
}
|
|
903
938
|
function getTemplateOverride(templateName, extension, data = {}) {
|
|
904
939
|
const overridePath = path.join(process.cwd(), '.textor', 'templates', `${templateName}${extension}`);
|
|
905
940
|
if (fs.existsSync(overridePath)) {
|
|
906
941
|
let content = fs.readFileSync(overridePath, 'utf-8');
|
|
907
|
-
|
|
908
|
-
|
|
942
|
+
const finalData = enrichData(data);
|
|
943
|
+
for (const [key, value] of Object.entries(finalData)) {
|
|
944
|
+
const replacement = () => value || '';
|
|
945
|
+
content = content.replace(new RegExp(`{{${key}}}`, 'g'), replacement);
|
|
946
|
+
content = content.replace(new RegExp(`__${key}__`, 'g'), replacement);
|
|
909
947
|
}
|
|
910
948
|
return content;
|
|
911
949
|
}
|
|
@@ -918,8 +956,8 @@ function getTemplateOverride(templateName, extension, data = {}) {
|
|
|
918
956
|
* - featureImportPath: Path to import the feature component
|
|
919
957
|
* - featureComponentName: Name of the feature component
|
|
920
958
|
*/
|
|
921
|
-
function generateRouteTemplate(layoutName, layoutImportPath, featureImportPath, featureComponentName) {
|
|
922
|
-
const override = getTemplateOverride('route',
|
|
959
|
+
function generateRouteTemplate(layoutName, layoutImportPath, featureImportPath, featureComponentName, extension = '.astro') {
|
|
960
|
+
const override = getTemplateOverride('route', extension, {
|
|
923
961
|
layoutName,
|
|
924
962
|
layoutImportPath,
|
|
925
963
|
featureImportPath,
|
|
@@ -950,9 +988,9 @@ import ${featureComponentName} from '${featureImportPath}';
|
|
|
950
988
|
* - componentName: Name of the feature component
|
|
951
989
|
* - scriptImportPath: Path to the feature's client-side script
|
|
952
990
|
*/
|
|
953
|
-
function generateFeatureTemplate(componentName, scriptImportPath, framework = 'astro') {
|
|
954
|
-
const
|
|
955
|
-
const override = getTemplateOverride('feature',
|
|
991
|
+
function generateFeatureTemplate(componentName, scriptImportPath, framework = 'astro', extension) {
|
|
992
|
+
const templateExtension = extension || (framework === 'astro' ? '.astro' : '.tsx');
|
|
993
|
+
const override = getTemplateOverride('feature', templateExtension, { componentName, scriptImportPath });
|
|
956
994
|
if (override)
|
|
957
995
|
return override;
|
|
958
996
|
if (framework === 'react') {
|
|
@@ -993,9 +1031,9 @@ function generateScriptsIndexTemplate() {
|
|
|
993
1031
|
* Component Template Variables:
|
|
994
1032
|
* - componentName: Name of the component
|
|
995
1033
|
*/
|
|
996
|
-
function generateComponentTemplate(componentName, framework = 'react') {
|
|
997
|
-
const
|
|
998
|
-
const override = getTemplateOverride('component',
|
|
1034
|
+
function generateComponentTemplate(componentName, framework = 'react', extension) {
|
|
1035
|
+
const templateExtension = extension || (framework === 'astro' ? '.astro' : '.tsx');
|
|
1036
|
+
const override = getTemplateOverride('component', templateExtension, { componentName });
|
|
999
1037
|
if (override)
|
|
1000
1038
|
return override;
|
|
1001
1039
|
if (framework === 'react') {
|
|
@@ -1720,11 +1758,11 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1720
1758
|
routeSignature = getSignature(config, 'typescript');
|
|
1721
1759
|
}
|
|
1722
1760
|
else {
|
|
1723
|
-
routeContent = generateRouteTemplate(options.layout, layoutImportPath, featureImportPath, featureComponentName);
|
|
1761
|
+
routeContent = generateRouteTemplate(options.layout, layoutImportPath, featureImportPath, featureComponentName, routeExtension);
|
|
1724
1762
|
routeSignature = getSignature(config, 'astro');
|
|
1725
1763
|
}
|
|
1726
1764
|
}
|
|
1727
|
-
const featureContent = generateFeatureTemplate(featureComponentName, scriptImportPath, framework);
|
|
1765
|
+
const featureContent = generateFeatureTemplate(featureComponentName, scriptImportPath, framework, config.naming.featureExtension);
|
|
1728
1766
|
const writtenFiles = [];
|
|
1729
1767
|
if (routeFilePath) {
|
|
1730
1768
|
const routeHash = await writeFileWithSignature(routeFilePath, routeContent, routeSignature, config.hashing?.normalization);
|
|
@@ -2556,7 +2594,7 @@ async function createComponentCommand(componentName, options) {
|
|
|
2556
2594
|
await ensureDir(servicesDirInside);
|
|
2557
2595
|
if (shouldCreateSchemas)
|
|
2558
2596
|
await ensureDir(schemasDirInside);
|
|
2559
|
-
const componentContent = generateComponentTemplate(normalizedName, framework);
|
|
2597
|
+
const componentContent = generateComponentTemplate(normalizedName, framework, config.naming.componentExtension);
|
|
2560
2598
|
const signature = getSignature(config, config.naming.componentExtension === '.astro' ? 'astro' : 'tsx');
|
|
2561
2599
|
const componentHash = await writeFileWithSignature(componentFilePath, componentContent, signature, config.hashing?.normalization);
|
|
2562
2600
|
await registerFile(componentFilePath, {
|
|
@@ -2780,13 +2818,14 @@ async function removeComponentCommand(identifier, options) {
|
|
|
2780
2818
|
console.log(`✓ Deleted component: ${componentDir}/`);
|
|
2781
2819
|
await cleanupEmptyDirs(path.dirname(componentDir), path.join(process.cwd(), config.paths.components));
|
|
2782
2820
|
// Unregister files
|
|
2783
|
-
const
|
|
2821
|
+
const relComponentPath = path.relative(process.cwd(), componentDir).replace(/\\/g, '/');
|
|
2822
|
+
const dirPrefix = relComponentPath + '/';
|
|
2784
2823
|
for (const f in state.files) {
|
|
2785
2824
|
if (f.startsWith(dirPrefix)) {
|
|
2786
2825
|
delete state.files[f];
|
|
2787
2826
|
}
|
|
2788
2827
|
}
|
|
2789
|
-
state.components = state.components.filter(c => c.name !== identifier && c.path !==
|
|
2828
|
+
state.components = state.components.filter(c => c.name !== identifier && c.path !== relComponentPath);
|
|
2790
2829
|
await saveState(state);
|
|
2791
2830
|
}
|
|
2792
2831
|
else if (result.message) {
|