@oamm/textor 1.0.2 → 1.0.3
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 +120 -9
- package/dist/bin/textor.js +336 -23
- package/dist/bin/textor.js.map +1 -1
- package/dist/index.cjs +180 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +180 -24
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -89,6 +89,42 @@ Configure this in .textor/config.json:
|
|
|
89
89
|
}
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
+
## File Naming Patterns
|
|
93
|
+
|
|
94
|
+
You can override generated file names for feature and component sub-files (api, services, hooks, tests, etc.) using simple patterns. Patterns support `{{componentName}}`, `{{hookName}}`, `{{hookExtension}}`, `{{testExtension}}`, `{{componentExtension}}`, and `{{featureExtension}}`.
|
|
95
|
+
|
|
96
|
+
Example:
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"filePatterns": {
|
|
100
|
+
"features": {
|
|
101
|
+
"api": "{{componentName}}.route.ts"
|
|
102
|
+
},
|
|
103
|
+
"components": {
|
|
104
|
+
"api": "{{componentName}}.route.ts"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
More examples:
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"filePatterns": {
|
|
114
|
+
"features": {
|
|
115
|
+
"hook": "use{{componentName}}.ts",
|
|
116
|
+
"test": "{{componentName}}.spec{{testExtension}}",
|
|
117
|
+
"readme": "{{componentName}}.md"
|
|
118
|
+
},
|
|
119
|
+
"components": {
|
|
120
|
+
"api": "{{componentName}}.route{{testExtension}}",
|
|
121
|
+
"services": "{{componentName}}.service{{hookExtension}}",
|
|
122
|
+
"stories": "{{componentName}}.stories.tsx"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
92
128
|
## 🛡️ Safety & File Tracking
|
|
93
129
|
|
|
94
130
|
Textor uses a multi-layered safety approach to protect your codebase.
|
|
@@ -223,6 +259,16 @@ pnpm textor adopt /users
|
|
|
223
259
|
pnpm textor adopt --all
|
|
224
260
|
```
|
|
225
261
|
|
|
262
|
+
### upgrade-config
|
|
263
|
+
Upgrade `.textor/config.json` to the latest schema version without recreating it.
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
pnpm textor upgrade-config
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Options:**
|
|
270
|
+
- `--dry-run`: Print the upgraded config without writing it.
|
|
271
|
+
|
|
226
272
|
## 🏗️ Technical Architecture
|
|
227
273
|
|
|
228
274
|
Textor is designed with enterprise-grade robustness, moving beyond simple scaffolding to provide a reliable refactoring engine.
|
|
@@ -269,9 +315,11 @@ When moving or renaming sections, Textor performs scoped AST-like updates:
|
|
|
269
315
|
## ⚙️ Configuration
|
|
270
316
|
|
|
271
317
|
The .textor/config.json file allows full control over the tool's behavior.
|
|
318
|
+
`configVersion` tracks schema changes and is updated by `textor upgrade-config`.
|
|
272
319
|
|
|
273
320
|
```json
|
|
274
321
|
{
|
|
322
|
+
"configVersion": 2,
|
|
275
323
|
"paths": {
|
|
276
324
|
"pages": "src/pages",
|
|
277
325
|
"features": "src/features",
|
|
@@ -402,16 +450,79 @@ The .textor/config.json file allows full control over the tool's behavior.
|
|
|
402
450
|
|
|
403
451
|
## 📝 Template Overrides
|
|
404
452
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
453
|
+
You can customize the code generated by Textor by providing your own templates. Textor looks for override files in the `.textor/templates/` directory at your project root.
|
|
454
|
+
|
|
455
|
+
### How to use Template Overrides
|
|
456
|
+
|
|
457
|
+
1. Create the `.textor/templates/` directory if it doesn't exist.
|
|
458
|
+
2. Create a file named according to the table below (e.g., `feature.astro` or `component.tsx`).
|
|
459
|
+
3. Use `{{variable}}` placeholders in your template. Textor will automatically replace them when generating files.
|
|
460
|
+
|
|
461
|
+
### Supported Templates
|
|
462
|
+
|
|
463
|
+
| Template Name | File to create in `.textor/templates/` | Available Variables |
|
|
464
|
+
| :--- | :--- | :--- |
|
|
465
|
+
| **Route** | `route.astro` | `{{layoutName}}`, `{{layoutImportPath}}`, `{{featureImportPath}}`, `{{featureComponentName}}` |
|
|
466
|
+
| **Feature** | `feature.astro` or `feature.tsx` | `{{componentName}}`, `{{scriptImportPath}}` |
|
|
467
|
+
| **Component** | `component.astro` or `component.tsx` | `{{componentName}}` |
|
|
468
|
+
| **Hook** | `hook.ts` | `{{componentName}}`, `{{hookName}}` |
|
|
469
|
+
| **Context** | `context.tsx` | `{{componentName}}` |
|
|
470
|
+
| **Test** | `test.tsx` | `{{componentName}}`, `{{componentPath}}` |
|
|
471
|
+
| **Index** | `index.ts` | `{{componentName}}`, `{{componentExtension}}` |
|
|
472
|
+
| **Types** | `types.ts` | `{{componentName}}` |
|
|
473
|
+
| **API** | `api.ts` | `{{componentName}}` |
|
|
474
|
+
| **Endpoint** | `endpoint.ts` | `{{componentName}}` |
|
|
475
|
+
| **Service** | `service.ts` | `{{componentName}}` |
|
|
476
|
+
| **Schema** | `schema.ts` | `{{componentName}}` |
|
|
477
|
+
| **Readme** | `readme.md` | `{{componentName}}` |
|
|
478
|
+
| **Stories** | `stories.tsx` | `{{componentName}}`, `{{componentPath}}` |
|
|
479
|
+
| **Config** | `config.ts` | `{{componentName}}` |
|
|
480
|
+
| **Constants** | `constants.ts` | `{{componentName}}` |
|
|
481
|
+
| **Scripts Index**| `scripts-index.ts` | (none) |
|
|
482
|
+
|
|
483
|
+
> **Note:** For `feature` and `component` templates, use the extension that matches your configured framework (`.astro` for Astro, `.tsx` for React). Other templates have fixed extensions for the override file, regardless of your project's configuration.
|
|
484
|
+
|
|
485
|
+
### Variables Description
|
|
486
|
+
|
|
487
|
+
- `{{componentName}}`: The PascalCase name of the feature or component (e.g., `UserCatalog`).
|
|
488
|
+
- `{{hookName}}`: The camelCase name of the generated hook (e.g., `useUserCatalog`).
|
|
489
|
+
- `{{componentPath}}`: Relative path to the component file (useful for imports in tests or stories).
|
|
490
|
+
- `{{featureComponentName}}`: The name of the feature component as imported in a route.
|
|
491
|
+
- `{{featureImportPath}}`: The import path for the feature component.
|
|
492
|
+
- `{{layoutName}}`: The name of the layout component being used.
|
|
493
|
+
- `{{layoutImportPath}}`: The import path for the layout component.
|
|
494
|
+
- `{{scriptImportPath}}`: Relative path to the client-side script entry point.
|
|
495
|
+
- `{{componentExtension}}`: The file extension of the component (e.g., `.astro` or `.tsx`).
|
|
496
|
+
|
|
497
|
+
### Example: Custom Feature Template (`.textor/templates/feature.astro`)
|
|
498
|
+
|
|
499
|
+
```astro
|
|
500
|
+
---
|
|
501
|
+
/**
|
|
502
|
+
* @generated by Textor
|
|
503
|
+
* Feature: {{componentName}}
|
|
504
|
+
*/
|
|
505
|
+
|
|
506
|
+
interface Props {
|
|
507
|
+
title?: string;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const { title = "{{componentName}}" } = Astro.props;
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
<section class="feature-{{componentName}}">
|
|
514
|
+
<h2>{title}</h2>
|
|
515
|
+
<slot />
|
|
516
|
+
</section>
|
|
517
|
+
|
|
518
|
+
<script src="{{scriptImportPath}}"></script>
|
|
413
519
|
|
|
414
|
-
|
|
520
|
+
<style>
|
|
521
|
+
.feature-{{componentName}} {
|
|
522
|
+
padding: 2rem;
|
|
523
|
+
}
|
|
524
|
+
</style>
|
|
525
|
+
```
|
|
415
526
|
|
|
416
527
|
---
|
|
417
528
|
|
package/dist/bin/textor.js
CHANGED
|
@@ -9,9 +9,11 @@ import { promisify } from 'util';
|
|
|
9
9
|
|
|
10
10
|
const CONFIG_DIR$1 = '.textor';
|
|
11
11
|
const CONFIG_FILE = 'config.json';
|
|
12
|
+
const CURRENT_CONFIG_VERSION = 2;
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* @typedef {Object} TextorConfig
|
|
16
|
+
* @property {number} configVersion
|
|
15
17
|
* @property {Object} paths
|
|
16
18
|
* @property {string} paths.pages
|
|
17
19
|
* @property {string} paths.features
|
|
@@ -48,6 +50,9 @@ const CONFIG_FILE = 'config.json';
|
|
|
48
50
|
* @property {boolean} components.createTypes
|
|
49
51
|
* @property {Object} formatting
|
|
50
52
|
* @property {string} formatting.tool
|
|
53
|
+
* @property {Object} filePatterns
|
|
54
|
+
* @property {Object} filePatterns.features
|
|
55
|
+
* @property {Object} filePatterns.components
|
|
51
56
|
* @property {Object} git
|
|
52
57
|
* @property {boolean} git.requireCleanRepo
|
|
53
58
|
* @property {boolean} git.stageChanges
|
|
@@ -60,6 +65,7 @@ const CONFIG_FILE = 'config.json';
|
|
|
60
65
|
* @type {TextorConfig}
|
|
61
66
|
*/
|
|
62
67
|
const DEFAULT_CONFIG = {
|
|
68
|
+
configVersion: CURRENT_CONFIG_VERSION,
|
|
63
69
|
paths: {
|
|
64
70
|
pages: 'src/pages',
|
|
65
71
|
features: 'src/features',
|
|
@@ -119,6 +125,34 @@ const DEFAULT_CONFIG = {
|
|
|
119
125
|
formatting: {
|
|
120
126
|
tool: 'none' // 'prettier' | 'biome' | 'none'
|
|
121
127
|
},
|
|
128
|
+
filePatterns: {
|
|
129
|
+
features: {
|
|
130
|
+
index: 'index.ts',
|
|
131
|
+
types: 'index.ts',
|
|
132
|
+
api: 'index.ts',
|
|
133
|
+
services: 'index.ts',
|
|
134
|
+
schemas: 'index.ts',
|
|
135
|
+
hook: '{{hookName}}{{hookExtension}}',
|
|
136
|
+
context: '{{componentName}}Context.tsx',
|
|
137
|
+
test: '{{componentName}}{{testExtension}}',
|
|
138
|
+
readme: 'README.md',
|
|
139
|
+
stories: '{{componentName}}.stories.tsx'
|
|
140
|
+
},
|
|
141
|
+
components: {
|
|
142
|
+
index: 'index.ts',
|
|
143
|
+
types: 'index.ts',
|
|
144
|
+
api: 'index.ts',
|
|
145
|
+
services: 'index.ts',
|
|
146
|
+
schemas: 'index.ts',
|
|
147
|
+
hook: '{{hookName}}{{hookExtension}}',
|
|
148
|
+
context: '{{componentName}}Context.tsx',
|
|
149
|
+
test: '{{componentName}}{{testExtension}}',
|
|
150
|
+
config: 'index.ts',
|
|
151
|
+
constants: 'index.ts',
|
|
152
|
+
readme: 'README.md',
|
|
153
|
+
stories: '{{componentName}}.stories.tsx'
|
|
154
|
+
}
|
|
155
|
+
},
|
|
122
156
|
hashing: {
|
|
123
157
|
normalization: 'normalizeEOL', // 'none' | 'normalizeEOL' | 'stripGeneratedRegions'
|
|
124
158
|
useMarkers: false
|
|
@@ -227,7 +261,7 @@ async function loadConfig() {
|
|
|
227
261
|
try {
|
|
228
262
|
const content = await readFile(configPath, 'utf-8');
|
|
229
263
|
const config = JSON.parse(content);
|
|
230
|
-
const merged =
|
|
264
|
+
const merged = mergeConfig(DEFAULT_CONFIG, config);
|
|
231
265
|
validateConfig(merged);
|
|
232
266
|
return merged;
|
|
233
267
|
} catch (error) {
|
|
@@ -266,6 +300,39 @@ async function saveConfig(config, force = false) {
|
|
|
266
300
|
return configPath;
|
|
267
301
|
}
|
|
268
302
|
|
|
303
|
+
function mergeConfig(defaults, config) {
|
|
304
|
+
return deepMerge(defaults, config);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function normalizeConfigVersion(config) {
|
|
308
|
+
if (!config || typeof config !== 'object') return config;
|
|
309
|
+
if (typeof config.configVersion !== 'number') {
|
|
310
|
+
return { ...config, configVersion: 1 };
|
|
311
|
+
}
|
|
312
|
+
return config;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function applyConfigMigrations(config) {
|
|
316
|
+
let current = normalizeConfigVersion(config);
|
|
317
|
+
let version = current.configVersion || 1;
|
|
318
|
+
let migrated = { ...current };
|
|
319
|
+
|
|
320
|
+
while (version < CURRENT_CONFIG_VERSION) {
|
|
321
|
+
if (version === 1) {
|
|
322
|
+
migrated = { ...migrated, configVersion: 2 };
|
|
323
|
+
version = 2;
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (migrated.configVersion !== CURRENT_CONFIG_VERSION) {
|
|
330
|
+
migrated.configVersion = CURRENT_CONFIG_VERSION;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return migrated;
|
|
334
|
+
}
|
|
335
|
+
|
|
269
336
|
/**
|
|
270
337
|
* Deeply merges source object into target object.
|
|
271
338
|
* @param {Object} target
|
|
@@ -298,6 +365,10 @@ function validateConfig(config) {
|
|
|
298
365
|
throw new Error('Invalid configuration: must be an object');
|
|
299
366
|
}
|
|
300
367
|
|
|
368
|
+
if (config.configVersion !== undefined && typeof config.configVersion !== 'number') {
|
|
369
|
+
throw new Error('Invalid configuration: "configVersion" must be a number');
|
|
370
|
+
}
|
|
371
|
+
|
|
301
372
|
const requiredSections = ['paths', 'naming', 'signatures', 'importAliases'];
|
|
302
373
|
for (const section of requiredSections) {
|
|
303
374
|
if (!config[section] || typeof config[section] !== 'object') {
|
|
@@ -309,6 +380,16 @@ function validateConfig(config) {
|
|
|
309
380
|
throw new Error('Invalid configuration: "kindRules" must be an array');
|
|
310
381
|
}
|
|
311
382
|
|
|
383
|
+
if (config.filePatterns && typeof config.filePatterns !== 'object') {
|
|
384
|
+
throw new Error('Invalid configuration: "filePatterns" must be an object');
|
|
385
|
+
}
|
|
386
|
+
if (config.filePatterns?.features && typeof config.filePatterns.features !== 'object') {
|
|
387
|
+
throw new Error('Invalid configuration: "filePatterns.features" must be an object');
|
|
388
|
+
}
|
|
389
|
+
if (config.filePatterns?.components && typeof config.filePatterns.components !== 'object') {
|
|
390
|
+
throw new Error('Invalid configuration: "filePatterns.components" must be an object');
|
|
391
|
+
}
|
|
392
|
+
|
|
312
393
|
// Validate paths are strings
|
|
313
394
|
for (const [key, value] of Object.entries(config.paths)) {
|
|
314
395
|
if (typeof value !== 'string') {
|
|
@@ -932,6 +1013,37 @@ var filesystem = /*#__PURE__*/Object.freeze({
|
|
|
932
1013
|
writeFileWithSignature: writeFileWithSignature
|
|
933
1014
|
});
|
|
934
1015
|
|
|
1016
|
+
function renderNamePattern(pattern, data = {}, label = 'pattern') {
|
|
1017
|
+
if (typeof pattern !== 'string') return null;
|
|
1018
|
+
const trimmed = pattern.trim();
|
|
1019
|
+
if (!trimmed) return null;
|
|
1020
|
+
|
|
1021
|
+
const missing = new Set();
|
|
1022
|
+
const rendered = trimmed.replace(/{{\s*([a-zA-Z0-9_]+)\s*}}/g, (match, key) => {
|
|
1023
|
+
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
1024
|
+
missing.add(key);
|
|
1025
|
+
return '';
|
|
1026
|
+
}
|
|
1027
|
+
return String(data[key]);
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
if (missing.size > 0) {
|
|
1031
|
+
throw new Error(
|
|
1032
|
+
`Invalid ${label}: missing values for ${Array.from(missing).join(', ')}`
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
return rendered;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
function resolvePatternedPath(baseDir, pattern, data, fallback, label) {
|
|
1040
|
+
const fileName = renderNamePattern(pattern, data, label) || fallback;
|
|
1041
|
+
if (!fileName) {
|
|
1042
|
+
throw new Error(`Invalid ${label}: resolved to empty file name`);
|
|
1043
|
+
}
|
|
1044
|
+
return secureJoin(baseDir, fileName);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
935
1047
|
function getTemplateOverride(templateName, extension, data = {}) {
|
|
936
1048
|
const overridePath = path.join(process.cwd(), '.textor', 'templates', `${templateName}${extension}`);
|
|
937
1049
|
if (existsSync(overridePath)) {
|
|
@@ -1614,16 +1726,86 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1614
1726
|
createIndex: shouldCreateIndex
|
|
1615
1727
|
} = effectiveOptions;
|
|
1616
1728
|
|
|
1617
|
-
const
|
|
1618
|
-
const
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1729
|
+
const featurePatterns = config.filePatterns?.features || {};
|
|
1730
|
+
const patternData = {
|
|
1731
|
+
componentName: featureComponentName,
|
|
1732
|
+
hookName: getHookFunctionName(featureComponentName),
|
|
1733
|
+
hookExtension: config.naming.hookExtension,
|
|
1734
|
+
testExtension: config.naming.testExtension,
|
|
1735
|
+
featureExtension: config.naming.featureExtension,
|
|
1736
|
+
componentExtension: config.naming.componentExtension
|
|
1737
|
+
};
|
|
1738
|
+
|
|
1739
|
+
const indexFilePath = resolvePatternedPath(
|
|
1740
|
+
featureDirPath,
|
|
1741
|
+
featurePatterns.index,
|
|
1742
|
+
patternData,
|
|
1743
|
+
'index.ts',
|
|
1744
|
+
'filePatterns.features.index'
|
|
1745
|
+
);
|
|
1746
|
+
const contextFilePath = resolvePatternedPath(
|
|
1747
|
+
contextDirInside,
|
|
1748
|
+
featurePatterns.context,
|
|
1749
|
+
patternData,
|
|
1750
|
+
`${featureComponentName}Context.tsx`,
|
|
1751
|
+
'filePatterns.features.context'
|
|
1752
|
+
);
|
|
1753
|
+
const hookFilePath = resolvePatternedPath(
|
|
1754
|
+
hooksDirInside,
|
|
1755
|
+
featurePatterns.hook,
|
|
1756
|
+
patternData,
|
|
1757
|
+
getHookFileName(featureComponentName, config.naming.hookExtension),
|
|
1758
|
+
'filePatterns.features.hook'
|
|
1759
|
+
);
|
|
1760
|
+
const testFilePath = resolvePatternedPath(
|
|
1761
|
+
testsDir,
|
|
1762
|
+
featurePatterns.test,
|
|
1763
|
+
patternData,
|
|
1764
|
+
`${featureComponentName}${config.naming.testExtension}`,
|
|
1765
|
+
'filePatterns.features.test'
|
|
1766
|
+
);
|
|
1767
|
+
const typesFilePath = resolvePatternedPath(
|
|
1768
|
+
typesDirInside,
|
|
1769
|
+
featurePatterns.types,
|
|
1770
|
+
patternData,
|
|
1771
|
+
'index.ts',
|
|
1772
|
+
'filePatterns.features.types'
|
|
1773
|
+
);
|
|
1774
|
+
const apiFilePath = resolvePatternedPath(
|
|
1775
|
+
apiDirInside,
|
|
1776
|
+
featurePatterns.api,
|
|
1777
|
+
patternData,
|
|
1778
|
+
'index.ts',
|
|
1779
|
+
'filePatterns.features.api'
|
|
1780
|
+
);
|
|
1781
|
+
const servicesFilePath = resolvePatternedPath(
|
|
1782
|
+
servicesDirInside,
|
|
1783
|
+
featurePatterns.services,
|
|
1784
|
+
patternData,
|
|
1785
|
+
'index.ts',
|
|
1786
|
+
'filePatterns.features.services'
|
|
1787
|
+
);
|
|
1788
|
+
const schemasFilePath = resolvePatternedPath(
|
|
1789
|
+
schemasDirInside,
|
|
1790
|
+
featurePatterns.schemas,
|
|
1791
|
+
patternData,
|
|
1792
|
+
'index.ts',
|
|
1793
|
+
'filePatterns.features.schemas'
|
|
1794
|
+
);
|
|
1795
|
+
const readmeFilePath = resolvePatternedPath(
|
|
1796
|
+
featureDirPath,
|
|
1797
|
+
featurePatterns.readme,
|
|
1798
|
+
patternData,
|
|
1799
|
+
'README.md',
|
|
1800
|
+
'filePatterns.features.readme'
|
|
1801
|
+
);
|
|
1802
|
+
const storiesFilePath = resolvePatternedPath(
|
|
1803
|
+
featureDirPath,
|
|
1804
|
+
featurePatterns.stories,
|
|
1805
|
+
patternData,
|
|
1806
|
+
`${featureComponentName}.stories.tsx`,
|
|
1807
|
+
'filePatterns.features.stories'
|
|
1808
|
+
);
|
|
1627
1809
|
|
|
1628
1810
|
const routeParts = normalizedRoute ? normalizedRoute.split('/').filter(Boolean) : [];
|
|
1629
1811
|
const reorganizations = [];
|
|
@@ -2693,20 +2875,102 @@ async function createComponentCommand(componentName, options) {
|
|
|
2693
2875
|
createReadme: shouldCreateReadme,
|
|
2694
2876
|
createStories: shouldCreateStories
|
|
2695
2877
|
} = effectiveOptions;
|
|
2878
|
+
|
|
2879
|
+
const componentPatterns = config.filePatterns?.components || {};
|
|
2880
|
+
const patternData = {
|
|
2881
|
+
componentName: normalizedName,
|
|
2882
|
+
hookName: getHookFunctionName(normalizedName),
|
|
2883
|
+
hookExtension: config.naming.hookExtension,
|
|
2884
|
+
testExtension: config.naming.testExtension,
|
|
2885
|
+
componentExtension: config.naming.componentExtension,
|
|
2886
|
+
featureExtension: config.naming.featureExtension
|
|
2887
|
+
};
|
|
2696
2888
|
|
|
2697
2889
|
const componentFilePath = path.join(componentDir, `${normalizedName}${config.naming.componentExtension}`);
|
|
2698
|
-
const indexFilePath =
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
const
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2890
|
+
const indexFilePath = resolvePatternedPath(
|
|
2891
|
+
componentDir,
|
|
2892
|
+
componentPatterns.index,
|
|
2893
|
+
patternData,
|
|
2894
|
+
'index.ts',
|
|
2895
|
+
'filePatterns.components.index'
|
|
2896
|
+
);
|
|
2897
|
+
const contextFilePath = resolvePatternedPath(
|
|
2898
|
+
contextDirInside,
|
|
2899
|
+
componentPatterns.context,
|
|
2900
|
+
patternData,
|
|
2901
|
+
`${normalizedName}Context.tsx`,
|
|
2902
|
+
'filePatterns.components.context'
|
|
2903
|
+
);
|
|
2904
|
+
const hookFilePath = resolvePatternedPath(
|
|
2905
|
+
hooksDirInside,
|
|
2906
|
+
componentPatterns.hook,
|
|
2907
|
+
patternData,
|
|
2908
|
+
getHookFileName(normalizedName, config.naming.hookExtension),
|
|
2909
|
+
'filePatterns.components.hook'
|
|
2910
|
+
);
|
|
2911
|
+
const testFilePath = resolvePatternedPath(
|
|
2912
|
+
testsDir,
|
|
2913
|
+
componentPatterns.test,
|
|
2914
|
+
patternData,
|
|
2915
|
+
`${normalizedName}${config.naming.testExtension}`,
|
|
2916
|
+
'filePatterns.components.test'
|
|
2917
|
+
);
|
|
2918
|
+
const configFilePath = resolvePatternedPath(
|
|
2919
|
+
configDirInside,
|
|
2920
|
+
componentPatterns.config,
|
|
2921
|
+
patternData,
|
|
2922
|
+
'index.ts',
|
|
2923
|
+
'filePatterns.components.config'
|
|
2924
|
+
);
|
|
2925
|
+
const constantsFilePath = resolvePatternedPath(
|
|
2926
|
+
constantsDirInside,
|
|
2927
|
+
componentPatterns.constants,
|
|
2928
|
+
patternData,
|
|
2929
|
+
'index.ts',
|
|
2930
|
+
'filePatterns.components.constants'
|
|
2931
|
+
);
|
|
2932
|
+
const typesFilePath = resolvePatternedPath(
|
|
2933
|
+
typesDirInside,
|
|
2934
|
+
componentPatterns.types,
|
|
2935
|
+
patternData,
|
|
2936
|
+
'index.ts',
|
|
2937
|
+
'filePatterns.components.types'
|
|
2938
|
+
);
|
|
2939
|
+
const apiFilePath = resolvePatternedPath(
|
|
2940
|
+
apiDirInside,
|
|
2941
|
+
componentPatterns.api,
|
|
2942
|
+
patternData,
|
|
2943
|
+
'index.ts',
|
|
2944
|
+
'filePatterns.components.api'
|
|
2945
|
+
);
|
|
2946
|
+
const servicesFilePath = resolvePatternedPath(
|
|
2947
|
+
servicesDirInside,
|
|
2948
|
+
componentPatterns.services,
|
|
2949
|
+
patternData,
|
|
2950
|
+
'index.ts',
|
|
2951
|
+
'filePatterns.components.services'
|
|
2952
|
+
);
|
|
2953
|
+
const schemasFilePath = resolvePatternedPath(
|
|
2954
|
+
schemasDirInside,
|
|
2955
|
+
componentPatterns.schemas,
|
|
2956
|
+
patternData,
|
|
2957
|
+
'index.ts',
|
|
2958
|
+
'filePatterns.components.schemas'
|
|
2959
|
+
);
|
|
2960
|
+
const readmeFilePath = resolvePatternedPath(
|
|
2961
|
+
componentDir,
|
|
2962
|
+
componentPatterns.readme,
|
|
2963
|
+
patternData,
|
|
2964
|
+
'README.md',
|
|
2965
|
+
'filePatterns.components.readme'
|
|
2966
|
+
);
|
|
2967
|
+
const storiesFilePath = resolvePatternedPath(
|
|
2968
|
+
componentDir,
|
|
2969
|
+
componentPatterns.stories,
|
|
2970
|
+
patternData,
|
|
2971
|
+
`${normalizedName}.stories.tsx`,
|
|
2972
|
+
'filePatterns.components.stories'
|
|
2973
|
+
);
|
|
2710
2974
|
|
|
2711
2975
|
if (options.dryRun) {
|
|
2712
2976
|
console.log('Dry run - would create:');
|
|
@@ -3725,6 +3989,49 @@ async function scanDirectoryOrFile(fullPath, fileSet, state) {
|
|
|
3725
3989
|
}
|
|
3726
3990
|
}
|
|
3727
3991
|
|
|
3992
|
+
async function upgradeConfigCommand(options) {
|
|
3993
|
+
try {
|
|
3994
|
+
const configPath = getConfigPath();
|
|
3995
|
+
|
|
3996
|
+
if (!existsSync(configPath)) {
|
|
3997
|
+
throw new Error(
|
|
3998
|
+
`Textor configuration not found at ${configPath}\n` +
|
|
3999
|
+
`Run 'textor init' to create it.`
|
|
4000
|
+
);
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
const rawContent = await readFile(configPath, 'utf-8');
|
|
4004
|
+
const rawConfig = JSON.parse(rawContent);
|
|
4005
|
+
const migrated = applyConfigMigrations(rawConfig);
|
|
4006
|
+
const merged = mergeConfig(DEFAULT_CONFIG, migrated);
|
|
4007
|
+
merged.configVersion = CURRENT_CONFIG_VERSION;
|
|
4008
|
+
|
|
4009
|
+
validateConfig(merged);
|
|
4010
|
+
|
|
4011
|
+
if (options.dryRun) {
|
|
4012
|
+
console.log('Dry run - upgraded configuration:');
|
|
4013
|
+
console.log(JSON.stringify(merged, null, 2));
|
|
4014
|
+
return;
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
await writeFile(configPath, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
|
|
4018
|
+
|
|
4019
|
+
console.log('Configuration upgraded successfully.');
|
|
4020
|
+
console.log(` Version: ${rawConfig.configVersion || 1} -> ${CURRENT_CONFIG_VERSION}`);
|
|
4021
|
+
console.log(` Path: ${configPath}`);
|
|
4022
|
+
} catch (error) {
|
|
4023
|
+
if (error instanceof SyntaxError) {
|
|
4024
|
+
console.error('Error: Failed to parse config: Invalid JSON');
|
|
4025
|
+
} else {
|
|
4026
|
+
console.error('Error:', error.message);
|
|
4027
|
+
}
|
|
4028
|
+
if (typeof process.exit === 'function' && process.env.NODE_ENV !== 'test') {
|
|
4029
|
+
process.exit(1);
|
|
4030
|
+
}
|
|
4031
|
+
throw error;
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
|
|
3728
4035
|
const program = new Command();
|
|
3729
4036
|
|
|
3730
4037
|
program
|
|
@@ -3844,5 +4151,11 @@ program
|
|
|
3844
4151
|
.option('--dry-run', 'Show what would be adopted without applying')
|
|
3845
4152
|
.action(adoptCommand);
|
|
3846
4153
|
|
|
4154
|
+
program
|
|
4155
|
+
.command('upgrade-config')
|
|
4156
|
+
.description('Upgrade Textor configuration to the latest version')
|
|
4157
|
+
.option('--dry-run', 'Show the upgraded config without writing to disk')
|
|
4158
|
+
.action(upgradeConfigCommand);
|
|
4159
|
+
|
|
3847
4160
|
program.parse();
|
|
3848
4161
|
//# sourceMappingURL=textor.js.map
|
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|