@starodubenko/fsd-gen 1.2.1-0 → 1.4.0-0
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 +47 -0
- package/dist/lib/reverse/analyze.d.ts.map +1 -1
- package/dist/lib/reverse/analyze.js +5 -3
- package/dist/lib/reverse/analyzeHelpers.d.ts +22 -1
- package/dist/lib/reverse/analyzeHelpers.d.ts.map +1 -1
- package/dist/lib/reverse/analyzeHelpers.js +92 -2
- package/dist/lib/reverse/buildHelpers.d.ts +4 -4
- package/dist/lib/reverse/buildHelpers.d.ts.map +1 -1
- package/dist/lib/reverse/buildHelpers.js +51 -7
- package/dist/lib/reverse/buildHelpers_fixed.d.ts +6 -0
- package/dist/lib/reverse/buildHelpers_fixed.d.ts.map +1 -0
- package/dist/lib/reverse/buildHelpers_fixed.js +56 -0
- package/dist/lib/reverse/buildHelpers_normalizeLayers.d.ts +6 -0
- package/dist/lib/reverse/buildHelpers_normalizeLayers.d.ts.map +1 -0
- package/dist/lib/reverse/buildHelpers_normalizeLayers.js +65 -0
- package/dist/lib/reverse/types.d.ts +24 -2
- package/dist/lib/reverse/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,53 @@ Reverse engineer existing code into reusable presets.
|
|
|
45
45
|
2. **Analyze**: `fsd-gen reverse:analyze <preset-name>`
|
|
46
46
|
3. **Build**: `fsd-gen reverse:build <preset-name> [--mode short|ejected]`
|
|
47
47
|
|
|
48
|
+
#### Configuration Examples
|
|
49
|
+
|
|
50
|
+
**Single Root:**
|
|
51
|
+
```typescript
|
|
52
|
+
export default {
|
|
53
|
+
root: 'src/entities/User',
|
|
54
|
+
targetLayer: 'entity'
|
|
55
|
+
};
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Multiple Roots with Conflict Resolution:**
|
|
59
|
+
```typescript
|
|
60
|
+
export default {
|
|
61
|
+
root: [
|
|
62
|
+
'src/entities/User',
|
|
63
|
+
'src/features/User', // Auto-resolved as User1
|
|
64
|
+
'src/widgets/User' // Auto-resolved as User2
|
|
65
|
+
],
|
|
66
|
+
targetLayer: 'entity'
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Multiple Layers with Array Support:**
|
|
71
|
+
```typescript
|
|
72
|
+
export default {
|
|
73
|
+
layers: [
|
|
74
|
+
{ root: 'src/entities/User', targetLayer: 'entity' },
|
|
75
|
+
{
|
|
76
|
+
root: ['src/features/Auth', 'src/features/Payment'],
|
|
77
|
+
targetLayer: 'feature'
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
};
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Features:**
|
|
84
|
+
- **Multi-Root Support**: Analyze multiple directories at once
|
|
85
|
+
- **Automatic Conflict Resolution**: Numeric suffixes (User, User1, User2)
|
|
86
|
+
- **Folder Name Normalization**: `user-action` → `UserAction`, `user_profile` → `UserProfile`
|
|
87
|
+
|
|
88
|
+
**Plural/Singular Recognition**: Automatically detects both forms of names:
|
|
89
|
+
- Analyzing "User" will also find "Users"
|
|
90
|
+
- Analyzing "Users" will also find "User"
|
|
91
|
+
- Works with common patterns: Category/Categories, Box/Boxes, etc.
|
|
92
|
+
- **TypeScript Config**: Type-safe `preset.config.ts` with enums
|
|
93
|
+
|
|
94
|
+
|
|
48
95
|
#### Modes
|
|
49
96
|
- **short** (default): Generates a thin `preset.ts` that auto-discovers templates at runtime.
|
|
50
97
|
- **ejected**: Compiles and copies all source files into the preset folder as static templates.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../../src/lib/reverse/analyze.ts"],"names":[],"mappings":"AAiBA;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../../src/lib/reverse/analyze.ts"],"names":[],"mappings":"AAiBA;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,iBAiHlF"}
|
|
@@ -26,8 +26,10 @@ export async function analyzeReversePreset(presetName, templatesDir) {
|
|
|
26
26
|
}
|
|
27
27
|
console.log(`Analyzing source at: ${rootPath} (Layer: ${layer.targetLayer})`);
|
|
28
28
|
// Normalize folder name to PascalCase for better token recognition
|
|
29
|
+
// Use resolvedName if provided (for conflict resolution), otherwise use basename
|
|
29
30
|
// e.g., "user-action" -> "UserAction"
|
|
30
|
-
const
|
|
31
|
+
const folderName = layer.resolvedName || basename(rootPath);
|
|
32
|
+
const subjectName = toPascalCase(folderName);
|
|
31
33
|
const variations = generateVariations(subjectName);
|
|
32
34
|
// Discovery
|
|
33
35
|
project.addSourceFilesAtPaths(join(rootPath, '**', '*.{ts,tsx,css,scss,less,sass}'));
|
|
@@ -78,8 +80,8 @@ export async function analyzeReversePreset(presetName, templatesDir) {
|
|
|
78
80
|
// Generate TypeScript content with enum values
|
|
79
81
|
const filesContent = resultFiles.map(file => {
|
|
80
82
|
const tokensStr = Object.entries(file.tokens)
|
|
81
|
-
.map(([key, value]) => `
|
|
82
|
-
.join(',\n');
|
|
83
|
+
.map(([key, value]) => `"${key}": ${tokenToEnum(value)}`)
|
|
84
|
+
.join(',\n ');
|
|
83
85
|
return ` {
|
|
84
86
|
"path": "${file.path}",
|
|
85
87
|
"targetLayer": ${layerToEnum(file.targetLayer)},
|
|
@@ -9,7 +9,18 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export declare function toPascalCase(str: string): string;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Simple pluralization - adds 's' or handles common patterns
|
|
13
|
+
* Examples: User -> Users, Category -> Categories, Box -> Boxes
|
|
14
|
+
*/
|
|
15
|
+
export declare function pluralize(word: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Simple singularization - removes 's' or handles common patterns
|
|
18
|
+
* Examples: Users -> User, Categories -> Category, Boxes -> Box
|
|
19
|
+
*/
|
|
20
|
+
export declare function singularize(word: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Generates naming variations for a given subject string.
|
|
23
|
+
* Now includes plural and singular forms.
|
|
13
24
|
*/
|
|
14
25
|
export declare function generateVariations(subject: string): {
|
|
15
26
|
pascal: string;
|
|
@@ -17,6 +28,16 @@ export declare function generateVariations(subject: string): {
|
|
|
17
28
|
lower: string;
|
|
18
29
|
upper: string;
|
|
19
30
|
kebab: string;
|
|
31
|
+
plural: string;
|
|
32
|
+
pluralCamel: string;
|
|
33
|
+
pluralLower: string;
|
|
34
|
+
pluralUpper: string;
|
|
35
|
+
pluralKebab: string;
|
|
36
|
+
singular: string;
|
|
37
|
+
singularCamel: string;
|
|
38
|
+
singularLower: string;
|
|
39
|
+
singularUpper: string;
|
|
40
|
+
singularKebab: string;
|
|
20
41
|
};
|
|
21
42
|
/**
|
|
22
43
|
* Identifies potential tokens in content based on variations
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzeHelpers.d.ts","sourceRoot":"","sources":["../../../src/lib/reverse/analyzeHelpers.ts"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAahD;AAED
|
|
1
|
+
{"version":3,"file":"analyzeHelpers.d.ts","sourceRoot":"","sources":["../../../src/lib/reverse/analyzeHelpers.ts"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAahD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAiB9C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoBhD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;EA4BjD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA2DzH;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,SAAS,GAAE,MAAW,GAAG,MAAM,CAMxG"}
|
|
@@ -22,7 +22,50 @@ export function toPascalCase(str) {
|
|
|
22
22
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* Simple pluralization - adds 's' or handles common patterns
|
|
26
|
+
* Examples: User -> Users, Category -> Categories, Box -> Boxes
|
|
27
|
+
*/
|
|
28
|
+
export function pluralize(word) {
|
|
29
|
+
if (!word)
|
|
30
|
+
return word;
|
|
31
|
+
// Check for special endings that need 'es' FIRST (like 'ss', 'x', 'ch', 'sh')
|
|
32
|
+
if (word.endsWith('ss') || word.endsWith('x') || word.endsWith('ch') || word.endsWith('sh')) {
|
|
33
|
+
return word + 'es'; // Class -> Classes, Box -> Boxes, Batch -> Batches
|
|
34
|
+
}
|
|
35
|
+
// Already plural (ends with 's' but not 'ss')
|
|
36
|
+
if (word.endsWith('s'))
|
|
37
|
+
return word;
|
|
38
|
+
// Common patterns
|
|
39
|
+
if (word.endsWith('y') && !['a', 'e', 'i', 'o', 'u'].includes(word[word.length - 2]?.toLowerCase())) {
|
|
40
|
+
return word.slice(0, -1) + 'ies'; // Category -> Categories
|
|
41
|
+
}
|
|
42
|
+
return word + 's'; // User -> Users
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Simple singularization - removes 's' or handles common patterns
|
|
46
|
+
* Examples: Users -> User, Categories -> Category, Boxes -> Box
|
|
47
|
+
*/
|
|
48
|
+
export function singularize(word) {
|
|
49
|
+
if (!word)
|
|
50
|
+
return word;
|
|
51
|
+
// Common patterns
|
|
52
|
+
if (word.endsWith('ies')) {
|
|
53
|
+
if (word.length > 4) {
|
|
54
|
+
return word.slice(0, -3) + 'y'; // Categories -> Category
|
|
55
|
+
}
|
|
56
|
+
// Short words like 'Ties' -> 'Tie' (remove 's') or handle as special
|
|
57
|
+
}
|
|
58
|
+
if (word.endsWith('xes') || word.endsWith('ches') || word.endsWith('shes') || word.endsWith('sses')) {
|
|
59
|
+
return word.slice(0, -2); // Boxes -> Box, Batches -> Batch
|
|
60
|
+
}
|
|
61
|
+
if (word.endsWith('s') && !word.endsWith('ss')) {
|
|
62
|
+
return word.slice(0, -1); // Users -> User
|
|
63
|
+
}
|
|
64
|
+
return word; // Already singular or special case
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Generates naming variations for a given subject string.
|
|
68
|
+
* Now includes plural and singular forms.
|
|
26
69
|
*/
|
|
27
70
|
export function generateVariations(subject) {
|
|
28
71
|
const pascal = subject.charAt(0).toUpperCase() + subject.slice(1);
|
|
@@ -31,7 +74,22 @@ export function generateVariations(subject) {
|
|
|
31
74
|
const upper = subject.toUpperCase();
|
|
32
75
|
// Simple kebab conversion (UserProfile -> user-profile)
|
|
33
76
|
const kebab = camel.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
34
|
-
|
|
77
|
+
// Generate plural and singular forms
|
|
78
|
+
const plural = pluralize(pascal);
|
|
79
|
+
const singular = singularize(pascal);
|
|
80
|
+
const pluralCamel = plural.charAt(0).toLowerCase() + plural.slice(1);
|
|
81
|
+
const pluralLower = plural.toLowerCase();
|
|
82
|
+
const pluralUpper = plural.toUpperCase();
|
|
83
|
+
const pluralKebab = pluralCamel.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
84
|
+
const singularCamel = singular.charAt(0).toLowerCase() + singular.slice(1);
|
|
85
|
+
const singularLower = singular.toLowerCase();
|
|
86
|
+
const singularUpper = singular.toUpperCase();
|
|
87
|
+
const singularKebab = singularCamel.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
88
|
+
return {
|
|
89
|
+
pascal, camel, lower, upper, kebab,
|
|
90
|
+
plural, pluralCamel, pluralLower, pluralUpper, pluralKebab,
|
|
91
|
+
singular, singularCamel, singularLower, singularUpper, singularKebab
|
|
92
|
+
};
|
|
35
93
|
}
|
|
36
94
|
/**
|
|
37
95
|
* Identifies potential tokens in content based on variations
|
|
@@ -54,6 +112,38 @@ export function identifyTokens(content, variations) {
|
|
|
54
112
|
if (variations.kebab !== variations.camel && content.includes(variations.kebab)) {
|
|
55
113
|
tokens[variations.kebab] = EntityToken.ENTITY_NAME_KEBAB;
|
|
56
114
|
}
|
|
115
|
+
// Add plural variations if different from original
|
|
116
|
+
if (variations.plural !== variations.pascal && content.includes(variations.plural)) {
|
|
117
|
+
tokens[variations.plural] = EntityToken.ENTITY_NAME;
|
|
118
|
+
}
|
|
119
|
+
if (variations.pluralCamel !== variations.camel && content.includes(variations.pluralCamel)) {
|
|
120
|
+
tokens[variations.pluralCamel] = EntityToken.ENTITY_NAME_CAMEL;
|
|
121
|
+
}
|
|
122
|
+
if (variations.pluralLower !== variations.lower && content.includes(variations.pluralLower)) {
|
|
123
|
+
tokens[variations.pluralLower] = EntityToken.ENTITY_NAME_LOWER;
|
|
124
|
+
}
|
|
125
|
+
if (variations.pluralUpper !== variations.upper && content.includes(variations.pluralUpper)) {
|
|
126
|
+
tokens[variations.pluralUpper] = EntityToken.ENTITY_NAME_UPPER;
|
|
127
|
+
}
|
|
128
|
+
if (variations.pluralKebab !== variations.kebab && content.includes(variations.pluralKebab)) {
|
|
129
|
+
tokens[variations.pluralKebab] = EntityToken.ENTITY_NAME_KEBAB;
|
|
130
|
+
}
|
|
131
|
+
// Add singular variations if different from original and plural
|
|
132
|
+
if (variations.singular !== variations.pascal && variations.singular !== variations.plural && content.includes(variations.singular)) {
|
|
133
|
+
tokens[variations.singular] = EntityToken.ENTITY_NAME;
|
|
134
|
+
}
|
|
135
|
+
if (variations.singularCamel !== variations.camel && variations.singularCamel !== variations.pluralCamel && content.includes(variations.singularCamel)) {
|
|
136
|
+
tokens[variations.singularCamel] = EntityToken.ENTITY_NAME_CAMEL;
|
|
137
|
+
}
|
|
138
|
+
if (variations.singularLower !== variations.lower && variations.singularLower !== variations.pluralLower && content.includes(variations.singularLower)) {
|
|
139
|
+
tokens[variations.singularLower] = EntityToken.ENTITY_NAME_LOWER;
|
|
140
|
+
}
|
|
141
|
+
if (variations.singularUpper !== variations.upper && variations.singularUpper !== variations.pluralUpper && content.includes(variations.singularUpper)) {
|
|
142
|
+
tokens[variations.singularUpper] = EntityToken.ENTITY_NAME_UPPER;
|
|
143
|
+
}
|
|
144
|
+
if (variations.singularKebab !== variations.kebab && variations.singularKebab !== variations.pluralKebab && content.includes(variations.singularKebab)) {
|
|
145
|
+
tokens[variations.singularKebab] = EntityToken.ENTITY_NAME_KEBAB;
|
|
146
|
+
}
|
|
57
147
|
return tokens;
|
|
58
148
|
}
|
|
59
149
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PresetConfig, PresetConfigFile, PresetConfigTokenMap, PresetSourceConfig,
|
|
1
|
+
import { PresetConfig, PresetConfigFile, PresetConfigTokenMap, PresetSourceConfig, NormalizedPresetSourceItem } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Loads the reverse engineering source configuration (preset.source.ts)
|
|
4
4
|
*/
|
|
@@ -17,7 +17,7 @@ export declare function loadReverseEnvironment(presetDir: string): Promise<{
|
|
|
17
17
|
/**
|
|
18
18
|
* Normalizes layer definitions from source configuration
|
|
19
19
|
*/
|
|
20
|
-
export declare function normalizeLayers(sourceConfig: PresetSourceConfig):
|
|
20
|
+
export declare function normalizeLayers(sourceConfig: PresetSourceConfig): NormalizedPresetSourceItem[];
|
|
21
21
|
/**
|
|
22
22
|
* Identifies the main entity token (mapping to {{name}} or {{entityName}})
|
|
23
23
|
*/
|
|
@@ -29,7 +29,7 @@ export declare function applyTokens(content: string, tokens: PresetConfigTokenMa
|
|
|
29
29
|
/**
|
|
30
30
|
* Guesses naming conventions (prefixes/suffixes) from source roots
|
|
31
31
|
*/
|
|
32
|
-
export declare function guessConventions(layers:
|
|
32
|
+
export declare function guessConventions(layers: NormalizedPresetSourceItem[], entityToken: string): Record<string, string>;
|
|
33
33
|
/**
|
|
34
34
|
* Generates preset.ts content for short mode
|
|
35
35
|
*/
|
|
@@ -37,5 +37,5 @@ export declare function generateShortPresetContent(helpers: Record<string, strin
|
|
|
37
37
|
/**
|
|
38
38
|
* Generates preset.ts content for ejected mode
|
|
39
39
|
*/
|
|
40
|
-
export declare function generateEjectedPresetContent(presetName: string, files: PresetConfigFile[], layers:
|
|
40
|
+
export declare function generateEjectedPresetContent(presetName: string, files: PresetConfigFile[], layers: NormalizedPresetSourceItem[], entityToken: string): string;
|
|
41
41
|
//# sourceMappingURL=buildHelpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildHelpers.d.ts","sourceRoot":"","sources":["../../../src/lib/reverse/buildHelpers.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"buildHelpers.d.ts","sourceRoot":"","sources":["../../../src/lib/reverse/buildHelpers.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,kBAAkB,EAAoB,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAEpJ;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAUrF;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAiB/E;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IACrE,YAAY,EAAE,kBAAkB,CAAC;IACjC,YAAY,EAAE,YAAY,CAAC;CAC9B,CAAC,CAID;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,kBAAkB,GAAG,0BAA0B,EAAE,CAoD9F;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,YAAY,GAAG,MAAM,CAcpE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAUjF;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,0BAA0B,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA2BlH;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAiBlF;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CACxC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,gBAAgB,EAAE,EACzB,MAAM,EAAE,0BAA0B,EAAE,EACpC,WAAW,EAAE,MAAM,GACpB,MAAM,CAqCR"}
|
|
@@ -46,17 +46,61 @@ export async function loadReverseEnvironment(presetDir) {
|
|
|
46
46
|
* Normalizes layer definitions from source configuration
|
|
47
47
|
*/
|
|
48
48
|
export function normalizeLayers(sourceConfig) {
|
|
49
|
-
const
|
|
49
|
+
const result = [];
|
|
50
50
|
if (sourceConfig.layers) {
|
|
51
|
-
|
|
51
|
+
for (const layer of sourceConfig.layers) {
|
|
52
|
+
const roots = Array.isArray(layer.root) ? layer.root : [layer.root];
|
|
53
|
+
if (roots.length === 1) {
|
|
54
|
+
result.push({ root: roots[0], targetLayer: layer.targetLayer });
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const basenames = roots.map(r => basename(r));
|
|
58
|
+
const nameCount = new Map();
|
|
59
|
+
const nameIndices = new Map();
|
|
60
|
+
basenames.forEach(name => { nameCount.set(name, (nameCount.get(name) || 0) + 1); });
|
|
61
|
+
roots.forEach((root, index) => {
|
|
62
|
+
const name = basenames[index];
|
|
63
|
+
const count = nameCount.get(name) || 1;
|
|
64
|
+
if (count > 1) {
|
|
65
|
+
const currentIndex = nameIndices.get(name) || 0;
|
|
66
|
+
nameIndices.set(name, currentIndex + 1);
|
|
67
|
+
const resolvedName = currentIndex === 0 ? name : `${name}${currentIndex}`;
|
|
68
|
+
result.push({ root, targetLayer: layer.targetLayer, resolvedName });
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
result.push({ root, targetLayer: layer.targetLayer });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
52
76
|
}
|
|
53
77
|
else if (sourceConfig.root) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
78
|
+
const roots = Array.isArray(sourceConfig.root) ? sourceConfig.root : [sourceConfig.root];
|
|
79
|
+
const targetLayer = sourceConfig.targetLayer || 'entity';
|
|
80
|
+
if (roots.length === 1) {
|
|
81
|
+
result.push({ root: roots[0], targetLayer });
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const basenames = roots.map(r => basename(r));
|
|
85
|
+
const nameCount = new Map();
|
|
86
|
+
const nameIndices = new Map();
|
|
87
|
+
basenames.forEach(name => { nameCount.set(name, (nameCount.get(name) || 0) + 1); });
|
|
88
|
+
roots.forEach((root, index) => {
|
|
89
|
+
const name = basenames[index];
|
|
90
|
+
const count = nameCount.get(name) || 1;
|
|
91
|
+
if (count > 1) {
|
|
92
|
+
const currentIndex = nameIndices.get(name) || 0;
|
|
93
|
+
nameIndices.set(name, currentIndex + 1);
|
|
94
|
+
const resolvedName = currentIndex === 0 ? name : `${name}${currentIndex}`;
|
|
95
|
+
result.push({ root, targetLayer, resolvedName });
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
result.push({ root, targetLayer });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
58
102
|
}
|
|
59
|
-
return
|
|
103
|
+
return result;
|
|
60
104
|
}
|
|
61
105
|
/**
|
|
62
106
|
* Identifies the main entity token (mapping to {{name}} or {{entityName}})
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes layer definitions from source configuration.
|
|
3
|
+
* Handles conflict resolution when multiple roots have the same folder name.
|
|
4
|
+
*/
|
|
5
|
+
export declare function normalizeLayers(sourceConfig: PresetSourceConfig): PresetSourceItem[];
|
|
6
|
+
//# sourceMappingURL=buildHelpers_fixed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildHelpers_fixed.d.ts","sourceRoot":"","sources":["../../../src/lib/reverse/buildHelpers_fixed.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,kBAAkB,GAAG,gBAAgB,EAAE,CAwDpF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes layer definitions from source configuration.
|
|
3
|
+
* Handles conflict resolution when multiple roots have the same folder name.
|
|
4
|
+
*/
|
|
5
|
+
export function normalizeLayers(sourceConfig) {
|
|
6
|
+
const layers = [];
|
|
7
|
+
if (sourceConfig.layers) {
|
|
8
|
+
layers.push(...sourceConfig.layers);
|
|
9
|
+
}
|
|
10
|
+
else if (sourceConfig.root) {
|
|
11
|
+
const roots = Array.isArray(sourceConfig.root) ? sourceConfig.root : [sourceConfig.root];
|
|
12
|
+
const targetLayer = sourceConfig.targetLayer || 'entity';
|
|
13
|
+
// If single root, no conflict possible
|
|
14
|
+
if (roots.length === 1) {
|
|
15
|
+
layers.push({
|
|
16
|
+
root: roots[0],
|
|
17
|
+
targetLayer
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
// Multiple roots: detect conflicts and append indices
|
|
22
|
+
const basenames = roots.map(r => basename(r));
|
|
23
|
+
const nameCount = new Map();
|
|
24
|
+
const nameIndices = new Map();
|
|
25
|
+
// Count occurrences of each basename
|
|
26
|
+
basenames.forEach(name => {
|
|
27
|
+
nameCount.set(name, (nameCount.get(name) || 0) + 1);
|
|
28
|
+
});
|
|
29
|
+
// Create layers with conflict-resolved names
|
|
30
|
+
roots.forEach((root, index) => {
|
|
31
|
+
const name = basenames[index];
|
|
32
|
+
const count = nameCount.get(name) || 1;
|
|
33
|
+
// If name appears multiple times, set resolvedName
|
|
34
|
+
if (count > 1) {
|
|
35
|
+
const currentIndex = nameIndices.get(name) || 0;
|
|
36
|
+
nameIndices.set(name, currentIndex + 1);
|
|
37
|
+
// First occurrence keeps original name, subsequent get index suffix
|
|
38
|
+
const resolvedName = currentIndex === 0 ? name : `${name}${currentIndex}`;
|
|
39
|
+
layers.push({
|
|
40
|
+
root,
|
|
41
|
+
targetLayer,
|
|
42
|
+
resolvedName
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// No conflict, no need for resolvedName
|
|
47
|
+
layers.push({
|
|
48
|
+
root,
|
|
49
|
+
targetLayer
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return layers;
|
|
56
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes layer definitions from source configuration.
|
|
3
|
+
* Expands layers with array roots and handles conflict resolution.
|
|
4
|
+
*/
|
|
5
|
+
export declare function normalizeLayers(sourceConfig: PresetSourceConfig): PresetSourceItem[];
|
|
6
|
+
//# sourceMappingURL=buildHelpers_normalizeLayers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildHelpers_normalizeLayers.d.ts","sourceRoot":"","sources":["../../../src/lib/reverse/buildHelpers_normalizeLayers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,kBAAkB,GAAG,gBAAgB,EAAE,CA8DpF"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes layer definitions from source configuration.
|
|
3
|
+
* Expands layers with array roots and handles conflict resolution.
|
|
4
|
+
*/
|
|
5
|
+
export function normalizeLayers(sourceConfig) {
|
|
6
|
+
const result = [];
|
|
7
|
+
if (sourceConfig.layers) {
|
|
8
|
+
// Expand each layer that might have array roots
|
|
9
|
+
for (const layer of sourceConfig.layers) {
|
|
10
|
+
const roots = Array.isArray(layer.root) ? layer.root : [layer.root];
|
|
11
|
+
if (roots.length === 1) {
|
|
12
|
+
result.push({ root: roots[0], targetLayer: layer.targetLayer });
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
// Multiple roots in this layer: detect conflicts and append indices
|
|
16
|
+
const basenames = roots.map(r => basename(r));
|
|
17
|
+
const nameCount = new Map();
|
|
18
|
+
const nameIndices = new Map();
|
|
19
|
+
basenames.forEach(name => {
|
|
20
|
+
nameCount.set(name, (nameCount.get(name) || 0) + 1);
|
|
21
|
+
});
|
|
22
|
+
roots.forEach((root, index) => {
|
|
23
|
+
const name = basenames[index];
|
|
24
|
+
const count = nameCount.get(name) || 1;
|
|
25
|
+
if (count > 1) {
|
|
26
|
+
const currentIndex = nameIndices.get(name) || 0;
|
|
27
|
+
nameIndices.set(name, currentIndex + 1);
|
|
28
|
+
const resolvedName = currentIndex === 0 ? name : `${name}${currentIndex}`;
|
|
29
|
+
result.push({ root, targetLayer: layer.targetLayer, resolvedName });
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
result.push({ root, targetLayer: layer.targetLayer });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else if (sourceConfig.root) {
|
|
39
|
+
const roots = Array.isArray(sourceConfig.root) ? sourceConfig.root : [sourceConfig.root];
|
|
40
|
+
const targetLayer = sourceConfig.targetLayer || 'entity';
|
|
41
|
+
if (roots.length === 1) {
|
|
42
|
+
result.push({ root: roots[0], targetLayer });
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const basenames = roots.map(r => basename(r));
|
|
46
|
+
const nameCount = new Map();
|
|
47
|
+
const nameIndices = new Map();
|
|
48
|
+
basenames.forEach(name => { nameCount.set(name, (nameCount.get(name) || 0) + 1); });
|
|
49
|
+
roots.forEach((root, index) => {
|
|
50
|
+
const name = basenames[index];
|
|
51
|
+
const count = nameCount.get(name) || 1;
|
|
52
|
+
if (count > 1) {
|
|
53
|
+
const currentIndex = nameIndices.get(name) || 0;
|
|
54
|
+
nameIndices.set(name, currentIndex + 1);
|
|
55
|
+
const resolvedName = currentIndex === 0 ? name : `${name}${currentIndex}`;
|
|
56
|
+
result.push({ root, targetLayer, resolvedName });
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
result.push({ root, targetLayer });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
import { EntityTokenValue, FsdLayerValue } from './constants.js';
|
|
2
2
|
export interface PresetSourceItem {
|
|
3
|
+
/**
|
|
4
|
+
* Root directory or array of root directories to analyze.
|
|
5
|
+
* If array is provided and folder names conflict, indices will be appended.
|
|
6
|
+
* Example: ['src/entities/User', 'src/features/User'] -> User, User1
|
|
7
|
+
*/
|
|
8
|
+
root: string | string[];
|
|
9
|
+
targetLayer: FsdLayerValue;
|
|
10
|
+
/**
|
|
11
|
+
* Resolved name for this source (used when multiple roots have conflicting names)
|
|
12
|
+
* If not set, basename of root is used
|
|
13
|
+
*/
|
|
14
|
+
resolvedName?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Normalized version of PresetSourceItem where root is always a string.
|
|
18
|
+
* This is returned by normalizeLayers() after expanding array roots.
|
|
19
|
+
*/
|
|
20
|
+
export interface NormalizedPresetSourceItem {
|
|
3
21
|
root: string;
|
|
4
22
|
targetLayer: FsdLayerValue;
|
|
23
|
+
resolvedName?: string;
|
|
5
24
|
}
|
|
6
25
|
export interface PresetSourceConfig {
|
|
7
26
|
/**
|
|
@@ -11,10 +30,13 @@ export interface PresetSourceConfig {
|
|
|
11
30
|
globalRoot?: string;
|
|
12
31
|
mode?: 'short' | 'ejected';
|
|
13
32
|
/**
|
|
14
|
-
* The root path of the reference code (etalon)
|
|
33
|
+
* The root path of the reference code (etalon).
|
|
34
|
+
* Can be a single path or array of paths.
|
|
35
|
+
* If array is provided and folder names conflict, indices will be appended.
|
|
36
|
+
* Example: ['src/entities/User', 'src/features/User'] -> User, User1
|
|
15
37
|
* @deprecated Use layers for multiple sources or simpler specific config
|
|
16
38
|
*/
|
|
17
|
-
root?: string;
|
|
39
|
+
root?: string | string[];
|
|
18
40
|
/**
|
|
19
41
|
* The target layer for the preset (default: 'entity')
|
|
20
42
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/reverse/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/reverse/types.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEjE,MAAM,WAAW,gBAAgB;IAC7B;;;;OAIG;IACH,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,aAAa,CAAC;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,aAAa,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IAC/B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAE3B;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAEzB;;OAEG;IACH,WAAW,CAAC,EAAE,aAAa,CAAC;IAE5B;;OAEG;IACH,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAE5B;;OAEG;IACH,OAAO,CAAC,EAAE;QACN,QAAQ,CAAC,EAAE,YAAY,GAAG,YAAY,CAAC;QACvC;;WAEG;QACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACL;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACjC,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,MAAM,CAAC;CACjD;AAED,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,aAAa,GAAG,MAAM,CAAC;IACpC,MAAM,EAAE,oBAAoB,CAAC;CAEhC;AAED,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAC7B"}
|
package/package.json
CHANGED