@starodubenko/fsd-gen 1.1.0-0 → 1.2.1-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 +72 -0
- package/dist/config/types.d.ts +8 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/lib/preset/actionExecution.js +1 -1
- package/dist/lib/reverse/analyze.d.ts.map +1 -1
- package/dist/lib/reverse/analyze.js +55 -8
- package/dist/lib/reverse/analyzeHelpers.d.ts +10 -0
- package/dist/lib/reverse/analyzeHelpers.d.ts.map +1 -1
- package/dist/lib/reverse/analyzeHelpers.js +34 -4
- package/dist/lib/reverse/analyzeHelpers.spec.d.ts +2 -0
- package/dist/lib/reverse/analyzeHelpers.spec.d.ts.map +1 -0
- package/dist/lib/reverse/analyzeHelpers.spec.js +56 -0
- package/dist/lib/reverse/buildHelpers.d.ts +2 -2
- package/dist/lib/reverse/buildHelpers.d.ts.map +1 -1
- package/dist/lib/reverse/buildHelpers.js +23 -9
- package/dist/lib/reverse/constants.d.ts +90 -0
- package/dist/lib/reverse/constants.d.ts.map +1 -0
- package/dist/lib/reverse/constants.js +85 -0
- package/dist/lib/reverse/types.d.ts +9 -4
- package/dist/lib/reverse/types.d.ts.map +1 -1
- package/dist/lib/templates/templateLoader.d.ts.map +1 -1
- package/dist/lib/templates/templateLoader.js +19 -6
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -49,6 +49,41 @@ Reverse engineer existing code into reusable presets.
|
|
|
49
49
|
- **short** (default): Generates a thin `preset.ts` that auto-discovers templates at runtime.
|
|
50
50
|
- **ejected**: Compiles and copies all source files into the preset folder as static templates.
|
|
51
51
|
|
|
52
|
+
#### TypeScript Preset Configs
|
|
53
|
+
|
|
54
|
+
The `reverse:analyze` command generates a type-safe `preset.config.ts` file using enums:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import type { ReversePresetConfig } from '@starodubenko/fsd-gen';
|
|
58
|
+
import { EntityToken, FsdLayer } from '@starodubenko/fsd-gen';
|
|
59
|
+
|
|
60
|
+
export default {
|
|
61
|
+
files: [{
|
|
62
|
+
path: "index.ts",
|
|
63
|
+
targetLayer: FsdLayer.ENTITY, // Type-safe layer
|
|
64
|
+
tokens: {
|
|
65
|
+
"User": EntityToken.NAME // Type-safe token
|
|
66
|
+
}
|
|
67
|
+
}]
|
|
68
|
+
} satisfies ReversePresetConfig;
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Available Enums:**
|
|
72
|
+
|
|
73
|
+
- **`EntityToken`**: Token types for code generation
|
|
74
|
+
- `NAME` / `ENTITY_NAME` - PascalCase (e.g., `User`)
|
|
75
|
+
- `ENTITY_NAME_CAMEL` - camelCase (e.g., `user`)
|
|
76
|
+
- `ENTITY_NAME_LOWER` - lowercase (e.g., `user`)
|
|
77
|
+
- `ENTITY_NAME_UPPER` - UPPERCASE (e.g., `USER`)
|
|
78
|
+
- `ENTITY_NAME_KEBAB` - kebab-case (e.g., `user-profile`)
|
|
79
|
+
|
|
80
|
+
- **`FsdLayer`**: FSD architectural layers
|
|
81
|
+
- `ENTITY` - Business entities
|
|
82
|
+
- `FEATURE` - User interactions
|
|
83
|
+
- `WIDGET` - Composite UI blocks
|
|
84
|
+
- `PAGE` - Application pages
|
|
85
|
+
- `SHARED` - Reusable utilities
|
|
86
|
+
|
|
52
87
|
## ⚙️ Configuration
|
|
53
88
|
|
|
54
89
|
Create an `fsdgen.config.ts` in your project root:
|
|
@@ -63,6 +98,13 @@ export default defineConfig({
|
|
|
63
98
|
*/
|
|
64
99
|
rootDir: 'src',
|
|
65
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Target directory for generated code.
|
|
103
|
+
* If not specified, defaults to rootDir.
|
|
104
|
+
* Useful for generating code to a different location.
|
|
105
|
+
*/
|
|
106
|
+
targetDir: 'src', // Optional: generate to a different directory
|
|
107
|
+
|
|
66
108
|
/**
|
|
67
109
|
* Alias configuration.
|
|
68
110
|
* Maps import aliases to their relative paths from root.
|
|
@@ -89,6 +131,36 @@ export default defineConfig({
|
|
|
89
131
|
});
|
|
90
132
|
```
|
|
91
133
|
|
|
134
|
+
**Configuration Options:**
|
|
135
|
+
|
|
136
|
+
- **`rootDir`** (default: `"src"`): Root directory of your source code. Used for path resolution and imports.
|
|
137
|
+
- **`targetDir`** (default: `rootDir`): Directory where generated code will be placed. Useful for generating to a different location than your source code.
|
|
138
|
+
- **`aliases`**: Import path aliases for FSD layers.
|
|
139
|
+
- **`templatesDir`**: Location of custom templates.
|
|
140
|
+
- **`naming`**: Naming convention enforcement level.
|
|
141
|
+
|
|
142
|
+
**Practical Examples:**
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// Example 1: Generate to a separate output directory
|
|
146
|
+
export default defineConfig({
|
|
147
|
+
rootDir: 'src',
|
|
148
|
+
targetDir: 'generated', // Code will be generated to ./generated/
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Example 2: Generate to a different project
|
|
152
|
+
export default defineConfig({
|
|
153
|
+
rootDir: 'src',
|
|
154
|
+
targetDir: '../backend/src', // Generate to sibling project
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Example 3: Default behavior (targetDir = rootDir)
|
|
158
|
+
export default defineConfig({
|
|
159
|
+
rootDir: 'src',
|
|
160
|
+
// targetDir not specified, defaults to 'src'
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
92
164
|
## 📂 Template Structure
|
|
93
165
|
|
|
94
166
|
To customize templates, creating a `templatesDir` (e.g., `.fsd-templates`) allows you to override default templates. The structure should mirror the generator's internal organization:
|
package/dist/config/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ACTION_TYPES, DISCOVERY_MODES, FSD_LAYERS, NAMING_MODES } from '../lib/constants.js';
|
|
2
2
|
export type Layer = (typeof FSD_LAYERS)[keyof typeof FSD_LAYERS];
|
|
3
3
|
export interface FsdGenConfig {
|
|
4
4
|
/**
|
|
@@ -6,6 +6,13 @@ export interface FsdGenConfig {
|
|
|
6
6
|
* @default "src"
|
|
7
7
|
*/
|
|
8
8
|
rootDir?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Target directory for generated code.
|
|
11
|
+
* If not specified, defaults to rootDir.
|
|
12
|
+
* Useful for generating code to a different location.
|
|
13
|
+
* @default rootDir
|
|
14
|
+
*/
|
|
15
|
+
targetDir?: string;
|
|
9
16
|
/**
|
|
10
17
|
* Alias configuration.
|
|
11
18
|
* Key is the alias (e.g., "@"), value is the path relative to root (e.g., "./src").
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"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"}
|
package/dist/config/types.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { defineConfig } from './config/defineConfig.js';
|
|
2
|
-
export { definePreset, type
|
|
2
|
+
export { definePreset, type ConventionConfig, type FsdGenConfig, type GeneratorContext, type PresetAction, type PresetConfig, type PresetConfigArgs, type PresetConfigFn, type PresetHelperOptions, type PresetHelpers, type TemplateContext } from './config/types.js';
|
|
3
3
|
export { createPresetHelpers } from './lib/helpers/presetHelpers.js';
|
|
4
|
-
export type
|
|
4
|
+
export { EntityToken, FsdLayer, isEntityToken, isFsdLayer, type EntityTokenValue, type FsdLayerValue } from './lib/reverse/constants.js';
|
|
5
|
+
export type { PresetConfigFile, PresetConfigTokenMap, PresetSourceConfig, PresetConfig as ReversePresetConfig } from './lib/reverse/types.js';
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,KAAK,gBAAgB,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAE,KAAK,mBAAmB,EAAE,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACxQ,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAGrE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,KAAK,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACzI,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,YAAY,IAAI,mBAAmB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { defineConfig } from './config/defineConfig.js';
|
|
2
2
|
export { definePreset } from './config/types.js';
|
|
3
3
|
export { createPresetHelpers } from './lib/helpers/presetHelpers.js';
|
|
4
|
+
// Reverse engineering exports
|
|
5
|
+
export { EntityToken, FsdLayer, isEntityToken, isFsdLayer } from './lib/reverse/constants.js';
|
|
@@ -124,7 +124,7 @@ export async function loadFileTemplate(templatePath, customTemplatesDir) {
|
|
|
124
124
|
* Execute a file action (create a file from template)
|
|
125
125
|
*/
|
|
126
126
|
export async function executeFileAction(action, variables, config) {
|
|
127
|
-
const targetPath = join(process.cwd(), config.rootDir, processTemplate(action.path, variables));
|
|
127
|
+
const targetPath = join(process.cwd(), config.targetDir || config.rootDir, processTemplate(action.path, variables));
|
|
128
128
|
await mkdir(dirname(targetPath), { recursive: true });
|
|
129
129
|
if (action.template) {
|
|
130
130
|
const context = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../../src/lib/reverse/analyze.ts"],"names":[],"mappings":"
|
|
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,iBA+GlF"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Project } from 'ts-morph';
|
|
2
|
-
import { join, resolve, basename, relative } from 'path';
|
|
3
|
-
import { writeFile } from 'fs/promises';
|
|
4
1
|
import { existsSync } from 'fs';
|
|
2
|
+
import { writeFile } from 'fs/promises';
|
|
3
|
+
import { basename, join, relative, resolve } from 'path';
|
|
4
|
+
import { Project } from 'ts-morph';
|
|
5
|
+
import { generateVariations, identifyTokens, resolveSourceRoot, toPascalCase } from './analyzeHelpers.js';
|
|
5
6
|
import { loadSourceConfig, normalizeLayers } from './buildHelpers.js';
|
|
6
|
-
import { generateVariations, identifyTokens, resolveSourceRoot } from './analyzeHelpers.js';
|
|
7
7
|
/**
|
|
8
8
|
* Analyzes source code and generates a preset configuration.
|
|
9
9
|
* Orchestrates the discovery of tokens and mapping of files to layers.
|
|
@@ -25,7 +25,9 @@ export async function analyzeReversePreset(presetName, templatesDir) {
|
|
|
25
25
|
throw new Error(`Source root path does not exist: ${rootPath}`);
|
|
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
|
+
// e.g., "user-action" -> "UserAction"
|
|
30
|
+
const subjectName = toPascalCase(basename(rootPath));
|
|
29
31
|
const variations = generateVariations(subjectName);
|
|
30
32
|
// Discovery
|
|
31
33
|
project.addSourceFilesAtPaths(join(rootPath, '**', '*.{ts,tsx,css,scss,less,sass}'));
|
|
@@ -47,9 +49,54 @@ export async function analyzeReversePreset(presetName, templatesDir) {
|
|
|
47
49
|
// Cleanup project for next layer
|
|
48
50
|
sourceFiles.forEach(f => project.removeSourceFile(f));
|
|
49
51
|
}
|
|
50
|
-
// Step 3: Save results
|
|
52
|
+
// Step 3: Save results as TypeScript file with enum values
|
|
51
53
|
const outputConfig = { files: resultFiles };
|
|
52
|
-
const outputPath = join(presetDir, 'preset.config.
|
|
53
|
-
|
|
54
|
+
const outputPath = join(presetDir, 'preset.config.ts');
|
|
55
|
+
// Helper to convert layer string to FsdLayer enum reference
|
|
56
|
+
const layerToEnum = (layer) => {
|
|
57
|
+
const layerMap = {
|
|
58
|
+
'entity': 'FsdLayer.ENTITY',
|
|
59
|
+
'feature': 'FsdLayer.FEATURE',
|
|
60
|
+
'widget': 'FsdLayer.WIDGET',
|
|
61
|
+
'page': 'FsdLayer.PAGE',
|
|
62
|
+
'shared': 'FsdLayer.SHARED'
|
|
63
|
+
};
|
|
64
|
+
return layerMap[layer] || `"${layer}"`;
|
|
65
|
+
};
|
|
66
|
+
// Helper to convert token value to EntityToken enum reference
|
|
67
|
+
const tokenToEnum = (tokenValue) => {
|
|
68
|
+
const tokenMap = {
|
|
69
|
+
'{{name}}': 'EntityToken.NAME',
|
|
70
|
+
'{{entityName}}': 'EntityToken.ENTITY_NAME',
|
|
71
|
+
'{{entityNameCamel}}': 'EntityToken.ENTITY_NAME_CAMEL',
|
|
72
|
+
'{{entityNameLower}}': 'EntityToken.ENTITY_NAME_LOWER',
|
|
73
|
+
'{{entityNameUpper}}': 'EntityToken.ENTITY_NAME_UPPER',
|
|
74
|
+
'{{entityNameKebab}}': 'EntityToken.ENTITY_NAME_KEBAB'
|
|
75
|
+
};
|
|
76
|
+
return tokenMap[tokenValue] || `"${tokenValue}"`;
|
|
77
|
+
};
|
|
78
|
+
// Generate TypeScript content with enum values
|
|
79
|
+
const filesContent = resultFiles.map(file => {
|
|
80
|
+
const tokensStr = Object.entries(file.tokens)
|
|
81
|
+
.map(([key, value]) => ` "${key}": ${tokenToEnum(value)}`)
|
|
82
|
+
.join(',\n');
|
|
83
|
+
return ` {
|
|
84
|
+
"path": "${file.path}",
|
|
85
|
+
"targetLayer": ${layerToEnum(file.targetLayer)},
|
|
86
|
+
"tokens": {
|
|
87
|
+
${tokensStr}
|
|
88
|
+
}
|
|
89
|
+
}`;
|
|
90
|
+
}).join(',\n');
|
|
91
|
+
const tsContent = `import type { ReversePresetConfig } from '@starodubenko/fsd-gen';
|
|
92
|
+
import { EntityToken, FsdLayer } from '@starodubenko/fsd-gen';
|
|
93
|
+
|
|
94
|
+
export default {
|
|
95
|
+
"files": [
|
|
96
|
+
${filesContent}
|
|
97
|
+
]
|
|
98
|
+
} satisfies ReversePresetConfig;
|
|
99
|
+
`;
|
|
100
|
+
await writeFile(outputPath, tsContent, 'utf-8');
|
|
54
101
|
console.log(`Analysis complete. Config written to: ${outputPath}`);
|
|
55
102
|
}
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a string to PascalCase.
|
|
3
|
+
* Handles kebab-case, snake_case, camelCase, and space-separated strings.
|
|
4
|
+
* Examples:
|
|
5
|
+
* "user-action" -> "UserAction"
|
|
6
|
+
* "user_profile" -> "UserProfile"
|
|
7
|
+
* "user action" -> "UserAction"
|
|
8
|
+
* "userAction" -> "UserAction"
|
|
9
|
+
*/
|
|
10
|
+
export declare function toPascalCase(str: string): string;
|
|
1
11
|
/**
|
|
2
12
|
* Generates naming variations for a given subject string
|
|
3
13
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzeHelpers.d.ts","sourceRoot":"","sources":["../../../src/lib/reverse/analyzeHelpers.ts"],"names":[],"mappings":"
|
|
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;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM;;;;;;EAUjD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAyBzH;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,SAAS,GAAE,MAAW,GAAG,MAAM,CAMxG"}
|
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
import { resolve } from 'path';
|
|
2
|
+
import { EntityToken } from './constants.js';
|
|
3
|
+
/**
|
|
4
|
+
* Converts a string to PascalCase.
|
|
5
|
+
* Handles kebab-case, snake_case, camelCase, and space-separated strings.
|
|
6
|
+
* Examples:
|
|
7
|
+
* "user-action" -> "UserAction"
|
|
8
|
+
* "user_profile" -> "UserProfile"
|
|
9
|
+
* "user action" -> "UserAction"
|
|
10
|
+
* "userAction" -> "UserAction"
|
|
11
|
+
*/
|
|
12
|
+
export function toPascalCase(str) {
|
|
13
|
+
// First, split by common delimiters (hyphens, underscores, spaces)
|
|
14
|
+
const words = str.split(/[-_\s]+/);
|
|
15
|
+
// If we got multiple words, capitalize each
|
|
16
|
+
if (words.length > 1) {
|
|
17
|
+
return words
|
|
18
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
19
|
+
.join('');
|
|
20
|
+
}
|
|
21
|
+
// If single word, just ensure first letter is uppercase
|
|
22
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
23
|
+
}
|
|
2
24
|
/**
|
|
3
25
|
* Generates naming variations for a given subject string
|
|
4
26
|
*/
|
|
@@ -17,13 +39,21 @@ export function generateVariations(subject) {
|
|
|
17
39
|
export function identifyTokens(content, variations) {
|
|
18
40
|
const tokens = {};
|
|
19
41
|
if (content.includes(variations.pascal)) {
|
|
20
|
-
tokens[variations.pascal] =
|
|
42
|
+
tokens[variations.pascal] = EntityToken.ENTITY_NAME;
|
|
21
43
|
}
|
|
22
|
-
// Only add camel if it differs from pascal (e.g. "User" vs "user"
|
|
44
|
+
// Only add camel if it differs from pascal (e.g. "User" vs "user")
|
|
23
45
|
if (variations.camel !== variations.pascal && content.includes(variations.camel)) {
|
|
24
|
-
tokens[variations.camel] =
|
|
46
|
+
tokens[variations.camel] = EntityToken.ENTITY_NAME_CAMEL;
|
|
47
|
+
}
|
|
48
|
+
if (variations.lower !== variations.camel && variations.lower !== variations.pascal && content.includes(variations.lower)) {
|
|
49
|
+
tokens[variations.lower] = EntityToken.ENTITY_NAME_LOWER;
|
|
50
|
+
}
|
|
51
|
+
if (variations.upper !== variations.pascal && content.includes(variations.upper)) {
|
|
52
|
+
tokens[variations.upper] = EntityToken.ENTITY_NAME_UPPER;
|
|
53
|
+
}
|
|
54
|
+
if (variations.kebab !== variations.camel && content.includes(variations.kebab)) {
|
|
55
|
+
tokens[variations.kebab] = EntityToken.ENTITY_NAME_KEBAB;
|
|
25
56
|
}
|
|
26
|
-
// We can add more variations here if needed (kebab, upper, etc.)
|
|
27
57
|
return tokens;
|
|
28
58
|
}
|
|
29
59
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzeHelpers.spec.d.ts","sourceRoot":"","sources":["../../../src/lib/reverse/analyzeHelpers.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { generateVariations, identifyTokens } from './analyzeHelpers';
|
|
3
|
+
describe('analyzeHelpers', () => {
|
|
4
|
+
describe('generateVariations', () => {
|
|
5
|
+
it('should generate all variations for a given subject', () => {
|
|
6
|
+
const result = generateVariations('UserProfile');
|
|
7
|
+
expect(result).toEqual({
|
|
8
|
+
pascal: 'UserProfile',
|
|
9
|
+
camel: 'userProfile',
|
|
10
|
+
lower: 'userprofile',
|
|
11
|
+
upper: 'USERPROFILE',
|
|
12
|
+
kebab: 'user-profile'
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
describe('identifyTokens', () => {
|
|
17
|
+
const variations = generateVariations('UserProfile');
|
|
18
|
+
it('should identify pascal case token', () => {
|
|
19
|
+
const content = 'class UserProfile {}';
|
|
20
|
+
const tokens = identifyTokens(content, variations);
|
|
21
|
+
expect(tokens['UserProfile']).toBe('{{entityName}}');
|
|
22
|
+
});
|
|
23
|
+
it('should identify camel case token', () => {
|
|
24
|
+
const content = 'const userProfile = new UserProfile();';
|
|
25
|
+
const tokens = identifyTokens(content, variations);
|
|
26
|
+
expect(tokens['userProfile']).toBe('{{entityNameCamel}}');
|
|
27
|
+
});
|
|
28
|
+
it('should identify lower case token', () => {
|
|
29
|
+
const content = 'const path = "/userprofile";';
|
|
30
|
+
const tokens = identifyTokens(content, variations);
|
|
31
|
+
expect(tokens['userprofile']).toBe('{{entityNameLower}}');
|
|
32
|
+
});
|
|
33
|
+
it('should identify upper case token', () => {
|
|
34
|
+
const content = 'const TYPE = "USERPROFILE";';
|
|
35
|
+
const tokens = identifyTokens(content, variations);
|
|
36
|
+
expect(tokens['USERPROFILE']).toBe('{{entityNameUpper}}');
|
|
37
|
+
});
|
|
38
|
+
it('should identify kebab case token', () => {
|
|
39
|
+
const content = 'div.user-profile {}';
|
|
40
|
+
const tokens = identifyTokens(content, variations);
|
|
41
|
+
expect(tokens['user-profile']).toBe('{{entityNameKebab}}');
|
|
42
|
+
});
|
|
43
|
+
it('should not add tokens if they are identical', () => {
|
|
44
|
+
// For a single word subject like "User"
|
|
45
|
+
const simpleVariations = generateVariations('User');
|
|
46
|
+
// User: pascal: User, camel: user, lower: user, upper: USER, kebab: user
|
|
47
|
+
// camel and lower and kebab are same "user"
|
|
48
|
+
const content = 'user';
|
|
49
|
+
const tokens = identifyTokens(content, simpleVariations);
|
|
50
|
+
// Should only have camel (or whatever comes first that matches)
|
|
51
|
+
expect(Object.keys(tokens)).toHaveLength(1);
|
|
52
|
+
expect(tokens['user']).toBe('{{entityNameCamel}}');
|
|
53
|
+
expect(tokens['UserProfile']).toBeUndefined();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { PresetConfig,
|
|
1
|
+
import { PresetConfig, PresetConfigFile, PresetConfigTokenMap, PresetSourceConfig, PresetSourceItem } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Loads the reverse engineering source configuration (preset.source.ts)
|
|
4
4
|
*/
|
|
5
5
|
export declare function loadSourceConfig(presetDir: string): Promise<PresetSourceConfig>;
|
|
6
6
|
/**
|
|
7
|
-
* Loads the reverse engineering preset configuration (preset.config.json)
|
|
7
|
+
* Loads the reverse engineering preset configuration (preset.config.ts or preset.config.json)
|
|
8
8
|
*/
|
|
9
9
|
export declare function loadPresetConfig(presetDir: string): Promise<PresetConfig>;
|
|
10
10
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildHelpers.d.ts","sourceRoot":"","sources":["../../../src/lib/reverse/buildHelpers.ts"],"names":[],"mappings":"
|
|
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,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAExH;;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,gBAAgB,EAAE,CAWpF;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,gBAAgB,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA2BxG;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,gBAAgB,EAAE,EAC1B,WAAW,EAAE,MAAM,GACpB,MAAM,CAqCR"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { readFile } from 'fs/promises';
|
|
2
|
-
import { join, basename } from 'path';
|
|
3
1
|
import { existsSync } from 'fs';
|
|
2
|
+
import { readFile } from 'fs/promises';
|
|
4
3
|
import { createJiti } from 'jiti';
|
|
4
|
+
import { basename, join } from 'path';
|
|
5
|
+
import { EntityToken } from './constants.js';
|
|
5
6
|
/**
|
|
6
7
|
* Loads the reverse engineering source configuration (preset.source.ts)
|
|
7
8
|
*/
|
|
@@ -15,15 +16,23 @@ export async function loadSourceConfig(presetDir) {
|
|
|
15
16
|
return configModule.default || configModule;
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
|
-
* Loads the reverse engineering preset configuration (preset.config.json)
|
|
19
|
+
* Loads the reverse engineering preset configuration (preset.config.ts or preset.config.json)
|
|
19
20
|
*/
|
|
20
21
|
export async function loadPresetConfig(presetDir) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// Try TypeScript config first
|
|
23
|
+
const tsConfigPath = join(presetDir, 'preset.config.ts');
|
|
24
|
+
if (existsSync(tsConfigPath)) {
|
|
25
|
+
const jiti = createJiti(import.meta.url);
|
|
26
|
+
const configModule = await jiti.import(tsConfigPath);
|
|
27
|
+
return configModule.default || configModule;
|
|
28
|
+
}
|
|
29
|
+
// Fallback to JSON config for backward compatibility
|
|
30
|
+
const jsonConfigPath = join(presetDir, 'preset.config.json');
|
|
31
|
+
if (existsSync(jsonConfigPath)) {
|
|
32
|
+
const presetConfigRaw = await readFile(jsonConfigPath, 'utf-8');
|
|
33
|
+
return JSON.parse(presetConfigRaw);
|
|
24
34
|
}
|
|
25
|
-
|
|
26
|
-
return JSON.parse(presetConfigRaw);
|
|
35
|
+
throw new Error(`Missing preset config file: ${tsConfigPath} or ${jsonConfigPath}`);
|
|
27
36
|
}
|
|
28
37
|
/**
|
|
29
38
|
* Loads both source and preset configurations
|
|
@@ -55,7 +64,12 @@ export function normalizeLayers(sourceConfig) {
|
|
|
55
64
|
export function detectEntityToken(presetConfig) {
|
|
56
65
|
for (const file of presetConfig.files) {
|
|
57
66
|
for (const [token, replacement] of Object.entries(file.tokens)) {
|
|
58
|
-
if (replacement ===
|
|
67
|
+
if (replacement === EntityToken.NAME ||
|
|
68
|
+
replacement === EntityToken.ENTITY_NAME ||
|
|
69
|
+
replacement === EntityToken.ENTITY_NAME_CAMEL ||
|
|
70
|
+
replacement === EntityToken.ENTITY_NAME_LOWER ||
|
|
71
|
+
replacement === EntityToken.ENTITY_NAME_UPPER ||
|
|
72
|
+
replacement === EntityToken.ENTITY_NAME_KEBAB) {
|
|
59
73
|
return token;
|
|
60
74
|
}
|
|
61
75
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity token types used in reverse engineering templates.
|
|
3
|
+
* These tokens are placeholders that get replaced during code generation.
|
|
4
|
+
*/
|
|
5
|
+
export declare const EntityToken: {
|
|
6
|
+
/**
|
|
7
|
+
* Base entity name in PascalCase.
|
|
8
|
+
* Example: "User", "Product", "OrderItem"
|
|
9
|
+
* Used in: class names, type names, component names
|
|
10
|
+
*/
|
|
11
|
+
readonly NAME: "{{name}}";
|
|
12
|
+
/**
|
|
13
|
+
* Entity name in PascalCase (alias for NAME).
|
|
14
|
+
* Example: "User", "Product", "OrderItem"
|
|
15
|
+
* Used in: class names, type names, component names
|
|
16
|
+
*/
|
|
17
|
+
readonly ENTITY_NAME: "{{entityName}}";
|
|
18
|
+
/**
|
|
19
|
+
* Entity name in camelCase.
|
|
20
|
+
* Example: "user", "product", "orderItem"
|
|
21
|
+
* Used in: variable names, function names, parameter names
|
|
22
|
+
*/
|
|
23
|
+
readonly ENTITY_NAME_CAMEL: "{{entityNameCamel}}";
|
|
24
|
+
/**
|
|
25
|
+
* Entity name in lowercase (no separators).
|
|
26
|
+
* Example: "user", "product", "orderitem"
|
|
27
|
+
* Used in: URLs, file paths, database table names
|
|
28
|
+
*/
|
|
29
|
+
readonly ENTITY_NAME_LOWER: "{{entityNameLower}}";
|
|
30
|
+
/**
|
|
31
|
+
* Entity name in UPPERCASE (no separators).
|
|
32
|
+
* Example: "USER", "PRODUCT", "ORDERITEM"
|
|
33
|
+
* Used in: constants, environment variables, enum values
|
|
34
|
+
*/
|
|
35
|
+
readonly ENTITY_NAME_UPPER: "{{entityNameUpper}}";
|
|
36
|
+
/**
|
|
37
|
+
* Entity name in kebab-case.
|
|
38
|
+
* Example: "user", "product", "order-item"
|
|
39
|
+
* Used in: CSS class names, HTML attributes, URLs
|
|
40
|
+
*/
|
|
41
|
+
readonly ENTITY_NAME_KEBAB: "{{entityNameKebab}}";
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Type representing any valid entity token value
|
|
45
|
+
*/
|
|
46
|
+
export type EntityTokenValue = typeof EntityToken[keyof typeof EntityToken];
|
|
47
|
+
/**
|
|
48
|
+
* Helper to check if a string is a valid entity token
|
|
49
|
+
*/
|
|
50
|
+
export declare function isEntityToken(value: string): value is EntityTokenValue;
|
|
51
|
+
/**
|
|
52
|
+
* FSD (Feature-Sliced Design) layer types.
|
|
53
|
+
* Represents the architectural layers in FSD methodology.
|
|
54
|
+
*/
|
|
55
|
+
export declare const FsdLayer: {
|
|
56
|
+
/**
|
|
57
|
+
* Entity layer - Business entities and domain models
|
|
58
|
+
* Example: User, Product, Order
|
|
59
|
+
*/
|
|
60
|
+
readonly ENTITY: "entity";
|
|
61
|
+
/**
|
|
62
|
+
* Feature layer - User interactions and business features
|
|
63
|
+
* Example: Authentication, ProductCatalog, Checkout
|
|
64
|
+
*/
|
|
65
|
+
readonly FEATURE: "feature";
|
|
66
|
+
/**
|
|
67
|
+
* Widget layer - Composite UI blocks
|
|
68
|
+
* Example: Header, Sidebar, ProductCard
|
|
69
|
+
*/
|
|
70
|
+
readonly WIDGET: "widget";
|
|
71
|
+
/**
|
|
72
|
+
* Page layer - Application pages/routes
|
|
73
|
+
* Example: HomePage, ProfilePage, ProductPage
|
|
74
|
+
*/
|
|
75
|
+
readonly PAGE: "page";
|
|
76
|
+
/**
|
|
77
|
+
* Shared layer - Reusable utilities and components
|
|
78
|
+
* Example: UI kit, helpers, constants
|
|
79
|
+
*/
|
|
80
|
+
readonly SHARED: "shared";
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Type representing any valid FSD layer value
|
|
84
|
+
*/
|
|
85
|
+
export type FsdLayerValue = typeof FsdLayer[keyof typeof FsdLayer];
|
|
86
|
+
/**
|
|
87
|
+
* Helper to check if a string is a valid FSD layer
|
|
88
|
+
*/
|
|
89
|
+
export declare function isFsdLayer(value: string): value is FsdLayerValue;
|
|
90
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/lib/reverse/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,WAAW;IACpB;;;;OAIG;;IAGH;;;;OAIG;;IAGH;;;;OAIG;;IAGH;;;;OAIG;;IAGH;;;;OAIG;;IAGH;;;;OAIG;;CAEG,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,WAAW,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAE5E;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,gBAAgB,CAEtE;AAED;;;GAGG;AACH,eAAO,MAAM,QAAQ;IACjB;;;OAGG;;IAGH;;;OAGG;;IAGH;;;OAGG;;IAGH;;;OAGG;;IAGH;;;OAGG;;CAEG,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,QAAQ,CAAC,MAAM,OAAO,QAAQ,CAAC,CAAC;AAEnE;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,aAAa,CAEhE"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity token types used in reverse engineering templates.
|
|
3
|
+
* These tokens are placeholders that get replaced during code generation.
|
|
4
|
+
*/
|
|
5
|
+
export const EntityToken = {
|
|
6
|
+
/**
|
|
7
|
+
* Base entity name in PascalCase.
|
|
8
|
+
* Example: "User", "Product", "OrderItem"
|
|
9
|
+
* Used in: class names, type names, component names
|
|
10
|
+
*/
|
|
11
|
+
NAME: '{{name}}',
|
|
12
|
+
/**
|
|
13
|
+
* Entity name in PascalCase (alias for NAME).
|
|
14
|
+
* Example: "User", "Product", "OrderItem"
|
|
15
|
+
* Used in: class names, type names, component names
|
|
16
|
+
*/
|
|
17
|
+
ENTITY_NAME: '{{entityName}}',
|
|
18
|
+
/**
|
|
19
|
+
* Entity name in camelCase.
|
|
20
|
+
* Example: "user", "product", "orderItem"
|
|
21
|
+
* Used in: variable names, function names, parameter names
|
|
22
|
+
*/
|
|
23
|
+
ENTITY_NAME_CAMEL: '{{entityNameCamel}}',
|
|
24
|
+
/**
|
|
25
|
+
* Entity name in lowercase (no separators).
|
|
26
|
+
* Example: "user", "product", "orderitem"
|
|
27
|
+
* Used in: URLs, file paths, database table names
|
|
28
|
+
*/
|
|
29
|
+
ENTITY_NAME_LOWER: '{{entityNameLower}}',
|
|
30
|
+
/**
|
|
31
|
+
* Entity name in UPPERCASE (no separators).
|
|
32
|
+
* Example: "USER", "PRODUCT", "ORDERITEM"
|
|
33
|
+
* Used in: constants, environment variables, enum values
|
|
34
|
+
*/
|
|
35
|
+
ENTITY_NAME_UPPER: '{{entityNameUpper}}',
|
|
36
|
+
/**
|
|
37
|
+
* Entity name in kebab-case.
|
|
38
|
+
* Example: "user", "product", "order-item"
|
|
39
|
+
* Used in: CSS class names, HTML attributes, URLs
|
|
40
|
+
*/
|
|
41
|
+
ENTITY_NAME_KEBAB: '{{entityNameKebab}}'
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Helper to check if a string is a valid entity token
|
|
45
|
+
*/
|
|
46
|
+
export function isEntityToken(value) {
|
|
47
|
+
return Object.values(EntityToken).includes(value);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* FSD (Feature-Sliced Design) layer types.
|
|
51
|
+
* Represents the architectural layers in FSD methodology.
|
|
52
|
+
*/
|
|
53
|
+
export const FsdLayer = {
|
|
54
|
+
/**
|
|
55
|
+
* Entity layer - Business entities and domain models
|
|
56
|
+
* Example: User, Product, Order
|
|
57
|
+
*/
|
|
58
|
+
ENTITY: 'entity',
|
|
59
|
+
/**
|
|
60
|
+
* Feature layer - User interactions and business features
|
|
61
|
+
* Example: Authentication, ProductCatalog, Checkout
|
|
62
|
+
*/
|
|
63
|
+
FEATURE: 'feature',
|
|
64
|
+
/**
|
|
65
|
+
* Widget layer - Composite UI blocks
|
|
66
|
+
* Example: Header, Sidebar, ProductCard
|
|
67
|
+
*/
|
|
68
|
+
WIDGET: 'widget',
|
|
69
|
+
/**
|
|
70
|
+
* Page layer - Application pages/routes
|
|
71
|
+
* Example: HomePage, ProfilePage, ProductPage
|
|
72
|
+
*/
|
|
73
|
+
PAGE: 'page',
|
|
74
|
+
/**
|
|
75
|
+
* Shared layer - Reusable utilities and components
|
|
76
|
+
* Example: UI kit, helpers, constants
|
|
77
|
+
*/
|
|
78
|
+
SHARED: 'shared'
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Helper to check if a string is a valid FSD layer
|
|
82
|
+
*/
|
|
83
|
+
export function isFsdLayer(value) {
|
|
84
|
+
return Object.values(FsdLayer).includes(value);
|
|
85
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { EntityTokenValue, FsdLayerValue } from './constants.js';
|
|
1
2
|
export interface PresetSourceItem {
|
|
2
3
|
root: string;
|
|
3
|
-
targetLayer:
|
|
4
|
+
targetLayer: FsdLayerValue;
|
|
4
5
|
}
|
|
5
6
|
export interface PresetSourceConfig {
|
|
6
7
|
/**
|
|
@@ -17,7 +18,7 @@ export interface PresetSourceConfig {
|
|
|
17
18
|
/**
|
|
18
19
|
* The target layer for the preset (default: 'entity')
|
|
19
20
|
*/
|
|
20
|
-
targetLayer?:
|
|
21
|
+
targetLayer?: FsdLayerValue;
|
|
21
22
|
/**
|
|
22
23
|
* Multiple sources for different layers
|
|
23
24
|
*/
|
|
@@ -33,12 +34,16 @@ export interface PresetSourceConfig {
|
|
|
33
34
|
ignore?: string[];
|
|
34
35
|
};
|
|
35
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Maps original strings found in source code to entity tokens
|
|
39
|
+
* Example: { "User": "{{entityName}}", "user": "{{entityNameCamel}}" }
|
|
40
|
+
*/
|
|
36
41
|
export interface PresetConfigTokenMap {
|
|
37
|
-
[original: string]: string;
|
|
42
|
+
[original: string]: EntityTokenValue | string;
|
|
38
43
|
}
|
|
39
44
|
export interface PresetConfigFile {
|
|
40
45
|
path: string;
|
|
41
|
-
targetLayer: string;
|
|
46
|
+
targetLayer: FsdLayerValue | string;
|
|
42
47
|
tokens: PresetConfigTokenMap;
|
|
43
48
|
}
|
|
44
49
|
export interface PresetConfig {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/reverse/types.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/reverse/types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEjE,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAkB;IAC/B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAE3B;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templateLoader.d.ts","sourceRoot":"","sources":["../../../src/lib/templates/templateLoader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"templateLoader.d.ts","sourceRoot":"","sources":["../../../src/lib/templates/templateLoader.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AASxD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,kBAAkB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAQzE;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACjC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAAE,GACrB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAexB;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,eAAe,KAAK,MAAM,CAAC,CAAC,CAuFzH;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,eAAe,KAAK,MAAM,CAAC,CAAC,CA0BtH;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAC9B,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,MAAa,EACnB,kBAAkB,CAAC,EAAE,MAAM,GAC5B,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,eAAe,KAAK,MAAM,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,eAAe,KAAK,MAAM,CAAC,CAAA;CAAE,CAAC,CAclI;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,eAAe,KAAK,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,eAAe,GAAG,MAAM,CAKlJ;AAED,wBAAsB,WAAW,CAAC,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAwBhF;AAED,wBAAsB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiB9G"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { readFile, readdir, stat } from 'fs/promises';
|
|
2
|
-
import { join, dirname, resolve } from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
2
|
import { createJiti } from 'jiti';
|
|
5
|
-
import {
|
|
3
|
+
import { dirname, join, resolve } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { PRESET_DIRS, TEMPLATE_FILES } from '../constants.js';
|
|
6
|
+
import { EntityToken } from '../reverse/constants.js';
|
|
6
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
8
|
const __dirname = dirname(__filename);
|
|
8
9
|
const jiti = createJiti(__filename);
|
|
@@ -44,7 +45,15 @@ export async function readComponentTemplate(templateDir) {
|
|
|
44
45
|
// Try to find dynamic template files
|
|
45
46
|
const extensions = ['.tsx', '.ts', '.js'];
|
|
46
47
|
// Check for Component.tsx or tokenized names like {{name}}.tsx
|
|
47
|
-
const possibleNames = [
|
|
48
|
+
const possibleNames = [
|
|
49
|
+
'Component',
|
|
50
|
+
EntityToken.NAME,
|
|
51
|
+
EntityToken.ENTITY_NAME,
|
|
52
|
+
EntityToken.ENTITY_NAME_CAMEL,
|
|
53
|
+
EntityToken.ENTITY_NAME_LOWER,
|
|
54
|
+
EntityToken.ENTITY_NAME_UPPER,
|
|
55
|
+
EntityToken.ENTITY_NAME_KEBAB
|
|
56
|
+
];
|
|
48
57
|
// Check for Component.tsx, {{name}}.tsx, etc. that are modules
|
|
49
58
|
for (const name of possibleNames) {
|
|
50
59
|
for (const ext of extensions) {
|
|
@@ -73,8 +82,12 @@ export async function readComponentTemplate(templateDir) {
|
|
|
73
82
|
}
|
|
74
83
|
const componentCandidates = allFiles
|
|
75
84
|
.map(f => typeof f === 'string' ? f : f.name)
|
|
76
|
-
.filter(f => f && (f.includes(
|
|
77
|
-
f.includes(
|
|
85
|
+
.filter(f => f && (f.includes(EntityToken.NAME) ||
|
|
86
|
+
f.includes(EntityToken.ENTITY_NAME) ||
|
|
87
|
+
f.includes(EntityToken.ENTITY_NAME_CAMEL) ||
|
|
88
|
+
f.includes(EntityToken.ENTITY_NAME_LOWER) ||
|
|
89
|
+
f.includes(EntityToken.ENTITY_NAME_UPPER) ||
|
|
90
|
+
f.includes(EntityToken.ENTITY_NAME_KEBAB) ||
|
|
78
91
|
(f.startsWith('Component') && (f.endsWith('.tsx') || f.endsWith('.ts') || f.endsWith('.js') || f.endsWith('.jsx')))));
|
|
79
92
|
for (const fileName of componentCandidates) {
|
|
80
93
|
// Skip styles
|
package/package.json
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@starodubenko/fsd-gen",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1-0",
|
|
4
4
|
"description": "A powerful CLI tool for scaffolding Feature-Sliced Design (FSD) components, slices, and layers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./reverse": {
|
|
14
|
+
"types": "./dist/lib/reverse/types.d.ts",
|
|
15
|
+
"import": "./dist/lib/reverse/types.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
8
18
|
"bin": {
|
|
9
19
|
"fsd-gen": "dist/cli.js"
|
|
10
20
|
},
|