@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 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:
@@ -1,4 +1,4 @@
1
- import { FSD_LAYERS, NAMING_MODES, ACTION_TYPES, DISCOVERY_MODES } from '../lib/constants.js';
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":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { NAMING_MODES, DEFAULT_ROOT_DIR } from '../lib/constants.js';
1
+ import { DEFAULT_ROOT_DIR, NAMING_MODES } from '../lib/constants.js';
2
2
  export const defaultConfig = {
3
3
  rootDir: DEFAULT_ROOT_DIR,
4
4
  aliases: { '@': './src' },
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { defineConfig } from './config/defineConfig.js';
2
- export { definePreset, type PresetConfig, type PresetConfigArgs, type PresetConfigFn, type FsdGenConfig, type ConventionConfig, type PresetAction, type TemplateContext, type PresetHelpers, type PresetHelperOptions, type GeneratorContext } from './config/types.js';
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 { PresetSourceConfig } from './lib/reverse/types.js';
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
@@ -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,YAAY,EAAE,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,KAAK,aAAa,EAAE,KAAK,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACxQ,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AACrE,YAAY,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC"}
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":"AAgBA;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,iBA2DlF"}
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
- const subjectName = basename(rootPath);
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.json');
53
- await writeFile(outputPath, JSON.stringify(outputConfig, null, 2), 'utf-8');
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":"AAGA;;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,CAczH;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,SAAS,GAAE,MAAW,GAAG,MAAM,CAMxG"}
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] = '{{entityName}}';
42
+ tokens[variations.pascal] = EntityToken.ENTITY_NAME;
21
43
  }
22
- // Only add camel if it differs from pascal (e.g. "User" vs "user" - wait, User/User is same)
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] = '{{entityNameCamel}}';
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=analyzeHelpers.spec.d.ts.map
@@ -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, PresetSourceConfig, PresetSourceItem, PresetConfigTokenMap, PresetConfigFile } from './types.js';
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":"AAKA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,oBAAoB,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,CAS/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,CASpE;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
+ {"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
- const configPath = join(presetDir, 'preset.config.json');
22
- if (!existsSync(configPath)) {
23
- throw new Error(`Missing preset config file: ${configPath}`);
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
- const presetConfigRaw = await readFile(configPath, 'utf-8');
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 === '{{name}}' || replacement === '{{entityName}}') {
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: 'entity' | 'feature' | 'widget' | 'page' | 'shared';
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?: 'entity' | 'feature' | 'widget' | 'page' | 'shared';
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,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;CACpE;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,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;IAElE;;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,MAAM,WAAW,oBAAoB;IACjC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,oBAAoB,CAAC;CAEhC;AAED,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAC7B"}
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":"AAKA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAOxD;;;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,CA2EzH;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
+ {"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 { TEMPLATE_FILES, PRESET_DIRS } from '../constants.js';
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 = ['Component', '{{name}}', '{{entityName}}'];
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('{{name}}') ||
77
- f.includes('{{entityName}}') ||
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.0-0",
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
  },