@starodubenko/fsd-gen 0.1.0 → 1.0.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.
Files changed (57) hide show
  1. package/README.md +172 -0
  2. package/dist/config/defineConfig.d.ts +1 -1
  3. package/dist/config/defineConfig.d.ts.map +1 -1
  4. package/dist/config/types.d.ts +59 -1
  5. package/dist/config/types.d.ts.map +1 -1
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/lib/generators/generate.d.ts +1 -5
  9. package/dist/lib/generators/generate.d.ts.map +1 -1
  10. package/dist/lib/generators/generate.js +2 -2
  11. package/dist/lib/helpers/presetHelpers.d.ts +1 -27
  12. package/dist/lib/helpers/presetHelpers.d.ts.map +1 -1
  13. package/dist/lib/helpers/presetHelpers.js +24 -9
  14. package/dist/lib/helpers/presetHelpers.spec.d.ts +2 -0
  15. package/dist/lib/helpers/presetHelpers.spec.d.ts.map +1 -0
  16. package/dist/lib/helpers/presetHelpers.spec.js +36 -0
  17. package/dist/lib/helpers/templateHelpers.d.ts +20 -0
  18. package/dist/lib/helpers/templateHelpers.d.ts.map +1 -0
  19. package/dist/lib/helpers/templateHelpers.js +23 -0
  20. package/dist/lib/jsx/components.d.ts +38 -0
  21. package/dist/lib/jsx/components.d.ts.map +1 -0
  22. package/dist/lib/jsx/components.js +40 -0
  23. package/dist/lib/jsx/jsx-runtime.d.ts +20 -0
  24. package/dist/lib/jsx/jsx-runtime.d.ts.map +1 -0
  25. package/dist/lib/jsx/jsx-runtime.js +7 -0
  26. package/dist/lib/jsx/renderer.d.ts +2 -0
  27. package/dist/lib/jsx/renderer.d.ts.map +1 -0
  28. package/dist/lib/jsx/renderer.js +24 -0
  29. package/dist/lib/preset/actionExecution.d.ts +2 -2
  30. package/dist/lib/preset/actionExecution.d.ts.map +1 -1
  31. package/dist/lib/preset/actionExecution.js +39 -4
  32. package/dist/lib/preset/actionExecution.spec.d.ts +2 -0
  33. package/dist/lib/preset/actionExecution.spec.d.ts.map +1 -0
  34. package/dist/lib/preset/actionExecution.spec.js +178 -0
  35. package/dist/lib/preset/presetDiscovery.d.ts +10 -4
  36. package/dist/lib/preset/presetDiscovery.d.ts.map +1 -1
  37. package/dist/lib/preset/presetDiscovery.js +29 -21
  38. package/dist/lib/preset/presetLoading.d.ts.map +1 -1
  39. package/dist/lib/preset/presetLoading.js +2 -1
  40. package/dist/lib/templates/templateLoader.d.ts +7 -5
  41. package/dist/lib/templates/templateLoader.d.ts.map +1 -1
  42. package/dist/lib/templates/templateLoader.js +44 -7
  43. package/package.json +2 -2
  44. package/templates/entity/model-ui-basic/Component.tsx +18 -7
  45. package/templates/feature/ui-model-basic/Component.tsx +18 -7
  46. package/templates/page/ui-basic/Component.tsx +18 -7
  47. package/templates/preset/table/entity/model.ts +10 -4
  48. package/templates/preset/table/feature/buttons.tsx +12 -6
  49. package/templates/preset/table/page/page.tsx +30 -8
  50. package/templates/preset/table/widget/table.tsx +28 -11
  51. package/templates/shared/ui-basic/Component.tsx +17 -6
  52. package/templates/widget/ui-basic/Component.tsx +18 -7
  53. package/templates/entity/model-ui-basic/Component.styles.ts +0 -1
  54. package/templates/feature/ui-model-basic/Component.styles.ts +0 -1
  55. package/templates/page/ui-basic/Component.styles.ts +0 -1
  56. package/templates/shared/ui-basic/Component.styles.ts +0 -1
  57. package/templates/widget/ui-basic/Component.styles.ts +0 -1
@@ -0,0 +1,24 @@
1
+ import { Fragment } from './jsx-runtime.js';
2
+ export function render(node) {
3
+ if (node === null || node === undefined || typeof node === 'boolean') {
4
+ return '';
5
+ }
6
+ if (typeof node === 'string' || typeof node === 'number') {
7
+ return String(node);
8
+ }
9
+ if (Array.isArray(node)) {
10
+ return node.map(render).join('');
11
+ }
12
+ if (node.type) {
13
+ const vnode = node;
14
+ if (vnode.type === Fragment) {
15
+ return render(vnode.props.children);
16
+ }
17
+ if (typeof vnode.type === 'function') {
18
+ return render(vnode.type(vnode.props));
19
+ }
20
+ // Handle undefined type or other cases if necessary
21
+ return '';
22
+ }
23
+ return '';
24
+ }
@@ -1,4 +1,4 @@
1
- import { PresetAction, PresetComponentAction, PresetFileAction, FsdGenConfig } from '../../config/types.js';
1
+ import { PresetAction, PresetComponentAction, PresetFileAction, FsdGenConfig, TemplateContext } from '../../config/types.js';
2
2
  /**
3
3
  * Prepare variables for an action by merging global and action-specific variables
4
4
  */
@@ -19,7 +19,7 @@ export declare function executeStylesAction(action: any, variables: Record<strin
19
19
  /**
20
20
  * Load a file template from the templates directory
21
21
  */
22
- export declare function loadFileTemplate(templatePath: string, customTemplatesDir?: string): Promise<string>;
22
+ export declare function loadFileTemplate(templatePath: string, customTemplatesDir?: string): Promise<string | ((context: TemplateContext) => string)>;
23
23
  /**
24
24
  * Execute a file action (create a file from template)
25
25
  */
@@ -1 +1 @@
1
- {"version":3,"file":"actionExecution.d.ts","sourceRoot":"","sources":["../../../src/lib/preset/actionExecution.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAK5G;;GAEG;AACH,wBAAgB,sBAAsB,CAClC,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAChC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAOrB;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CACxC,MAAM,EAAE,qBAAqB,EAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9B,MAAM,EAAE,YAAY,GACrB,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACnC,MAAM,EAAE,GAAG,EAAE,iEAAiE;AAC9E,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9B,MAAM,EAAE,YAAY,GACrB,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACrC,MAAM,EAAE,GAAG,EACX,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9B,MAAM,EAAE,YAAY,GACrB,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAClC,YAAY,EAAE,MAAM,EACpB,kBAAkB,CAAC,EAAE,MAAM,GAC5B,OAAO,CAAC,MAAM,CAAC,CAoBjB;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACnC,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9B,MAAM,EAAE,YAAY,GACrB,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAChC,OAAO,EAAE,YAAY,EAAE,EACvB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC/B,MAAM,EAAE,YAAY,GACrB,OAAO,CAAC,IAAI,CAAC,CAqBf"}
1
+ {"version":3,"file":"actionExecution.d.ts","sourceRoot":"","sources":["../../../src/lib/preset/actionExecution.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAM7H;;GAEG;AACH,wBAAgB,sBAAsB,CAClC,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAChC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAOrB;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CACxC,MAAM,EAAE,qBAAqB,EAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9B,MAAM,EAAE,YAAY,GACrB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACnC,MAAM,EAAE,GAAG,EAAE,iEAAiE;AAC9E,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9B,MAAM,EAAE,YAAY,GACrB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACrC,MAAM,EAAE,GAAG,EACX,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9B,MAAM,EAAE,YAAY,GACrB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAClC,YAAY,EAAE,MAAM,EACpB,kBAAkB,CAAC,EAAE,MAAM,GAC5B,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,eAAe,KAAK,MAAM,CAAC,CAAC,CAiC1D;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACnC,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9B,MAAM,EAAE,YAAY,GACrB,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAChC,OAAO,EAAE,YAAY,EAAE,EACvB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC/B,MAAM,EAAE,YAAY,GACrB,OAAO,CAAC,IAAI,CAAC,CAqBf"}
@@ -7,6 +7,7 @@
7
7
  import { join, resolve, dirname, basename } from 'path';
8
8
  import { writeFile, mkdir, readFile } from 'fs/promises';
9
9
  import { fileURLToPath } from 'url';
10
+ import { createJiti } from 'jiti';
10
11
  import { updateBarrel } from '../barrels/updateBarrels.js';
11
12
  import { ACTION_TYPES, FILE_EXTENSIONS } from '../constants.js';
12
13
  import { generateComponent, generateHook, generateStyles } from '../generators/generate.js';
@@ -14,6 +15,7 @@ import { resolveFsdPaths } from '../naming/resolvePaths.js';
14
15
  import { processTemplate } from '../templates/templateLoader.js';
15
16
  const __filename = fileURLToPath(import.meta.url);
16
17
  const __dirname = dirname(__filename);
18
+ const jiti = createJiti(__filename);
17
19
  /**
18
20
  * Prepare variables for an action by merging global and action-specific variables
19
21
  */
@@ -36,7 +38,11 @@ export async function executeComponentAction(action, variables, config) {
36
38
  ...variables,
37
39
  componentName,
38
40
  sliceName,
39
- layer: action.layer,
41
+ template: {
42
+ componentName,
43
+ sliceName,
44
+ layer: action.layer,
45
+ },
40
46
  };
41
47
  await generateComponent(paths, context, action.template, config.templatesDir);
42
48
  }
@@ -52,7 +58,11 @@ variables, config) {
52
58
  ...variables,
53
59
  componentName,
54
60
  sliceName,
55
- layer: action.layer,
61
+ template: {
62
+ componentName,
63
+ sliceName,
64
+ layer: action.layer,
65
+ },
56
66
  };
57
67
  await generateHook(paths, context, action.template, config.templatesDir);
58
68
  }
@@ -67,7 +77,11 @@ export async function executeStylesAction(action, variables, config) {
67
77
  ...variables,
68
78
  componentName,
69
79
  sliceName,
70
- layer: action.layer,
80
+ template: {
81
+ componentName,
82
+ sliceName,
83
+ layer: action.layer,
84
+ },
71
85
  };
72
86
  await generateStyles(paths, context, action.template, config.templatesDir);
73
87
  }
@@ -83,6 +97,19 @@ export async function loadFileTemplate(templatePath, customTemplatesDir) {
83
97
  pathsToCheck.push(join(internalTemplatesDir, templatePath));
84
98
  for (const p of pathsToCheck) {
85
99
  try {
100
+ // Try loading as a module first if it's a TS/JS file
101
+ if (p.endsWith('.ts') || p.endsWith('.tsx') || p.endsWith('.js')) {
102
+ try {
103
+ const module = await jiti.import(p);
104
+ if (typeof module.default === 'function') {
105
+ console.log(`Loaded file template (module) from: ${p}`);
106
+ return module.default;
107
+ }
108
+ }
109
+ catch {
110
+ // Not a module or failed to load, fall back to string read
111
+ }
112
+ }
86
113
  const content = await readFile(p, 'utf-8');
87
114
  console.log(`Loaded file template from: ${p}`);
88
115
  return content;
@@ -100,8 +127,16 @@ export async function executeFileAction(action, variables, config) {
100
127
  const targetPath = join(process.cwd(), config.rootDir, processTemplate(action.path, variables));
101
128
  await mkdir(dirname(targetPath), { recursive: true });
102
129
  if (action.template) {
130
+ const context = {
131
+ ...variables,
132
+ template: {
133
+ componentName: variables.componentName,
134
+ sliceName: variables.sliceName || '',
135
+ layer: typeof variables.layer === 'string' ? variables.layer : '',
136
+ },
137
+ };
103
138
  const content = await loadFileTemplate(action.template, config.templatesDir);
104
- const processed = processTemplate(content, variables);
139
+ const processed = processTemplate(content, context);
105
140
  await writeFile(targetPath, processed);
106
141
  console.log(`Created ${targetPath}`);
107
142
  // Update barrel for the file (only for TypeScript files)
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=actionExecution.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actionExecution.spec.d.ts","sourceRoot":"","sources":["../../../src/lib/preset/actionExecution.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,178 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { executeComponentAction, executeHookAction, executeStylesAction, executeFileAction } from './actionExecution';
3
+ import { ACTION_TYPES, FSD_LAYERS } from '../constants';
4
+ import * as generateModule from '../generators/generate';
5
+ import * as templateLoader from '../templates/templateLoader';
6
+ import * as resolvePaths from '../naming/resolvePaths';
7
+ // Mock dependencies
8
+ vi.mock('../generators/generate');
9
+ vi.mock('../templates/templateLoader');
10
+ vi.mock('../naming/resolvePaths');
11
+ describe('actionExecution', () => {
12
+ beforeEach(() => {
13
+ vi.clearAllMocks();
14
+ // Setup default mocks
15
+ vi.spyOn(templateLoader, 'processTemplate').mockImplementation((tmpl) => tmpl);
16
+ vi.spyOn(resolvePaths, 'resolveFsdPaths').mockReturnValue({
17
+ layerPath: '/mock/entities',
18
+ slicePath: '/mock/entities/User',
19
+ uiPath: '/mock/entities/User/ui',
20
+ componentPath: '/mock/entities/User/ui/UserCard'
21
+ });
22
+ });
23
+ describe('executeComponentAction', () => {
24
+ it('should pass a complete GeneratorContext to generateComponent', async () => {
25
+ const config = { rootDir: 'src', templatesDir: '.templates' };
26
+ const action = {
27
+ type: ACTION_TYPES.COMPONENT,
28
+ layer: FSD_LAYERS.ENTITY,
29
+ slice: 'User',
30
+ name: 'UserCard',
31
+ template: 'user-card.tsx'
32
+ };
33
+ // Mimic variables object populated by createPresetHelpers + globalVars
34
+ const variables = {
35
+ base: {
36
+ baseName: 'User',
37
+ name: 'User',
38
+ },
39
+ layer: {
40
+ entity: {
41
+ importPath: '@entities/User',
42
+ apiPath: '@entities/User/ui',
43
+ },
44
+ features: {
45
+ slice: 'ManageUser',
46
+ importPath: '@features/ManageUser',
47
+ },
48
+ widget: {
49
+ slice: 'UserTable',
50
+ importPath: '@widgets/UserTable',
51
+ },
52
+ page: {
53
+ slice: 'UserPage',
54
+ importPath: '@pages/UserPage',
55
+ },
56
+ },
57
+ extraGlobal: 'something'
58
+ };
59
+ await executeComponentAction(action, variables, config);
60
+ // Assert generateComponent was called
61
+ expect(generateModule.generateComponent).toHaveBeenCalled();
62
+ // Capture the context passed to generateComponent
63
+ const callArgs = vi.mocked(generateModule.generateComponent).mock.calls[0];
64
+ const context = callArgs[1];
65
+ // Verify template keys
66
+ expect(context.template.componentName).toBe('UserCard');
67
+ expect(context.template.sliceName).toBe('User');
68
+ expect(context.template.layer).toBe(FSD_LAYERS.ENTITY);
69
+ // Verify keys from PresetHelpers (passed in via variables)
70
+ expect(context.base.name).toBe('User');
71
+ expect(context.layer.entity.apiPath).toBe('@entities/User/ui');
72
+ // Verify it retains other variables
73
+ expect(context).toHaveProperty('extraGlobal', 'something');
74
+ });
75
+ });
76
+ // Shared variables for all tests
77
+ const commonVariables = {
78
+ base: {
79
+ baseName: 'User',
80
+ name: 'User',
81
+ },
82
+ layer: {
83
+ entity: {
84
+ importPath: '@entities/User',
85
+ apiPath: '@entities/User/ui',
86
+ },
87
+ features: {
88
+ slice: 'ManageUser',
89
+ importPath: '@features/ManageUser',
90
+ },
91
+ widget: {
92
+ slice: 'UserTable',
93
+ importPath: '@widgets/UserTable',
94
+ },
95
+ page: {
96
+ slice: 'UserPage',
97
+ importPath: '@pages/UserPage',
98
+ },
99
+ },
100
+ extraGlobal: 'something'
101
+ };
102
+ const commonConfig = { rootDir: 'src', templatesDir: '.templates' };
103
+ describe('executeHookAction', () => {
104
+ it('should pass a complete GeneratorContext to generateHook', async () => {
105
+ const action = {
106
+ type: ACTION_TYPES.HOOK,
107
+ layer: FSD_LAYERS.ENTITY,
108
+ slice: 'User',
109
+ name: 'useUser',
110
+ template: 'use-user.ts'
111
+ };
112
+ await executeHookAction(action, commonVariables, commonConfig);
113
+ expect(generateModule.generateHook).toHaveBeenCalled();
114
+ const callArgs = vi.mocked(generateModule.generateHook).mock.calls[0];
115
+ const context = callArgs[1];
116
+ // Verify template keys
117
+ expect(context.template.componentName).toBe('useUser');
118
+ expect(context.template.sliceName).toBe('User');
119
+ expect(context.template.layer).toBe(FSD_LAYERS.ENTITY);
120
+ // Verify keys from PresetHelpers
121
+ expect(context.base.name).toBe('User');
122
+ expect(context.layer.entity.apiPath).toBe('@entities/User/ui');
123
+ });
124
+ });
125
+ describe('executeStylesAction', () => {
126
+ it('should pass a complete GeneratorContext to generateStyles', async () => {
127
+ const action = {
128
+ type: ACTION_TYPES.STYLES,
129
+ layer: FSD_LAYERS.ENTITY,
130
+ slice: 'User',
131
+ name: 'UserCard',
132
+ template: 'user-card.module.css'
133
+ };
134
+ await executeStylesAction(action, commonVariables, commonConfig);
135
+ expect(generateModule.generateStyles).toHaveBeenCalled();
136
+ const callArgs = vi.mocked(generateModule.generateStyles).mock.calls[0];
137
+ const context = callArgs[1];
138
+ // Verify template keys (name falls back to slice if not provided, but here we provide it)
139
+ expect(context.template.componentName).toBe('UserCard');
140
+ expect(context.template.sliceName).toBe('User');
141
+ expect(context.template.layer).toBe(FSD_LAYERS.ENTITY);
142
+ // Verify keys from PresetHelpers
143
+ expect(context.base.name).toBe('User');
144
+ });
145
+ });
146
+ describe('executeFileAction', () => {
147
+ it('should pass a complete GeneratorContext to template processing', async () => {
148
+ const action = {
149
+ type: ACTION_TYPES.FILE,
150
+ path: 'model/types.ts',
151
+ template: 'types.ts'
152
+ };
153
+ // loadFileTemplate mock to return a string template
154
+ vi.spyOn(require('./actionExecution'), 'loadFileTemplate').mockResolvedValue('template content');
155
+ // Mock mkDir and writeFile to avoid FS errors
156
+ const fsMock = await import('fs/promises');
157
+ vi.spyOn(fsMock, 'mkdir').mockResolvedValue(undefined);
158
+ vi.spyOn(fsMock, 'writeFile').mockResolvedValue(undefined);
159
+ // executeFileAction uses processTemplate to resolve the path AND the content
160
+ // We want to capture the context used when processing the CONTENT
161
+ // The first call is for path resolution, second for content
162
+ await executeFileAction(action, { ...commonVariables, componentName: 'User', sliceName: 'User' }, commonConfig);
163
+ expect(templateLoader.processTemplate).toHaveBeenCalledTimes(2);
164
+ // The second call to processTemplate should receive the context
165
+ const contentCallArgs = vi.mocked(templateLoader.processTemplate).mock.calls[1];
166
+ const context = contentCallArgs[1];
167
+ // Verify template keys
168
+ expect(context.template).toBeDefined();
169
+ expect(context.template.componentName).toBe('User');
170
+ expect(context.template.sliceName).toBe('User');
171
+ // Layer might be empty string for generic files if not provided in variables
172
+ expect(context.template.layer).toBeDefined();
173
+ // Verify keys from PresetHelpers
174
+ expect(context.base.name).toBe('User');
175
+ expect(context.layer.entity.apiPath).toBe('@entities/User/ui');
176
+ });
177
+ });
178
+ });
@@ -19,15 +19,21 @@ export declare function createEntityApiActions(apiDir: string, entityName: strin
19
19
  /**
20
20
  * Create component actions for feature buttons
21
21
  */
22
- export declare function createFeatureButtonActions(buttonsDir: string, entityName: string, presetName: string, conventions: ConventionConfig): Promise<PresetAction[]>;
23
22
  /**
24
- * Create component action for widget table
23
+ * Create component actions for feature sub-components (grouped by directory)
24
+ */
25
+ export declare function createFeatureActions(featureDir: string, entityName: string, presetName: string, conventions: ConventionConfig): Promise<PresetAction[]>;
26
+ /**
27
+ * Create component action for widget
28
+ */
29
+ export declare function createWidgetAction(entityName: string, presetName: string, conventions: ConventionConfig, templateDir: string): PresetComponentAction;
30
+ /**
31
+ * Create component action for page
25
32
  */
26
- export declare function createWidgetTableAction(entityName: string, presetName: string, conventions: ConventionConfig): PresetComponentAction;
27
33
  /**
28
34
  * Create component action for page
29
35
  */
30
- export declare function createPageAction(entityName: string, presetName: string, conventions: ConventionConfig): PresetComponentAction;
36
+ export declare function createPageAction(entityName: string, presetName: string, conventions: ConventionConfig, templateDir: string): PresetComponentAction;
31
37
  /**
32
38
  * Create component action for shared
33
39
  */
@@ -1 +1 @@
1
- {"version":3,"file":"presetDiscovery.d.ts","sourceRoot":"","sources":["../../../src/lib/preset/presetDiscovery.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAEjC,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAYhH;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAW5F;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC5B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACnB,gBAAgB,CASlB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAChC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACnB,qBAAqB,CAQvB;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CACxC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACnB,OAAO,CAAC,YAAY,EAAE,CAAC,CAuBzB;AAED;;GAEG;AACH,wBAAsB,0BAA0B,CAC5C,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,gBAAgB,GAC9B,OAAO,CAAC,YAAY,EAAE,CAAC,CAoBzB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACnC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,gBAAgB,GAC9B,qBAAqB,CAUvB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,gBAAgB,GAC9B,qBAAqB,CAUvB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAC9B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACnB,qBAAqB,CAUvB;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACnC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,GAAE,gBAAqB,GACnC,OAAO,CAAC,YAAY,EAAE,CAAC,CAqDzB"}
1
+ {"version":3,"file":"presetDiscovery.d.ts","sourceRoot":"","sources":["../../../src/lib/preset/presetDiscovery.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAEjC,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAYhH;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAW5F;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC5B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACnB,gBAAgB,CASlB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAChC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACnB,qBAAqB,CAQvB;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CACxC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACnB,OAAO,CAAC,YAAY,EAAE,CAAC,CAuBzB;AAED;;GAEG;AACH;;GAEG;AACH,wBAAsB,oBAAoB,CACtC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,gBAAgB,GAC9B,OAAO,CAAC,YAAY,EAAE,CAAC,CAoBzB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAC9B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,gBAAgB,EAC7B,WAAW,EAAE,MAAM,GACpB,qBAAqB,CAUvB;AAED;;GAEG;AACH;;GAEG;AACH,wBAAgB,gBAAgB,CAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,gBAAgB,EAC7B,WAAW,EAAE,MAAM,GACpB,qBAAqB,CAUvB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAC9B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACnB,qBAAqB,CAUvB;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACnC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,GAAE,gBAAqB,GACnC,OAAO,CAAC,YAAY,EAAE,CAAC,CAuDzB"}
@@ -11,7 +11,7 @@
11
11
  * Converts the file system structure of a preset into a list of executable generator actions.
12
12
  */
13
13
  import { readdir, stat } from 'fs/promises';
14
- import { join } from 'path';
14
+ import { join, basename } from 'path';
15
15
  import { LAYER_PLURALS, FSD_LAYERS, FSD_SEGMENTS, FILE_EXTENSIONS, PRESET_DIRS, API_HOOK_PREFIXES, API_OPERATIONS, ACTION_TYPES } from '../constants.js';
16
16
  /**
17
17
  * Scan a layer directory for entries
@@ -79,49 +79,55 @@ export async function createEntityApiActions(apiDir, entityName, presetName) {
79
79
  /**
80
80
  * Create component actions for feature buttons
81
81
  */
82
- export async function createFeatureButtonActions(buttonsDir, entityName, presetName, conventions) {
82
+ /**
83
+ * Create component actions for feature sub-components (grouped by directory)
84
+ */
85
+ export async function createFeatureActions(featureDir, entityName, presetName, conventions) {
83
86
  const actions = [];
84
87
  const featurePrefix = conventions.featureSlicePrefix ?? 'Manage';
85
- const buttonEntries = await readdir(buttonsDir, { withFileTypes: true });
86
- for (const buttonEntry of buttonEntries) {
87
- if (buttonEntry.isDirectory()) {
88
- const buttonType = buttonEntry.name; // create, edit, delete
89
- const capitalizedType = buttonType.charAt(0).toUpperCase() + buttonType.slice(1);
88
+ const featureEntries = await readdir(featureDir, { withFileTypes: true });
89
+ for (const featureEntry of featureEntries) {
90
+ if (featureEntry.isDirectory()) {
91
+ const featureName = featureEntry.name;
92
+ const capitalizedName = featureName.charAt(0).toUpperCase() + featureName.slice(1);
90
93
  actions.push({
91
94
  type: ACTION_TYPES.COMPONENT,
92
95
  layer: FSD_LAYERS.FEATURE,
93
96
  slice: `${featurePrefix}${entityName}`,
94
- name: `${capitalizedType}${entityName}Button`,
95
- template: `${PRESET_DIRS.ROOT}/${presetName}/${FSD_LAYERS.FEATURE}/${PRESET_DIRS.BUTTONS}/${buttonEntry.name}`
97
+ name: `${capitalizedName}${entityName}`,
98
+ template: `${PRESET_DIRS.ROOT}/${presetName}/${FSD_LAYERS.FEATURE}/${basename(featureDir)}/${featureEntry.name}`
96
99
  });
97
100
  }
98
101
  }
99
102
  return actions;
100
103
  }
101
104
  /**
102
- * Create component action for widget table
105
+ * Create component action for widget
103
106
  */
104
- export function createWidgetTableAction(entityName, presetName, conventions) {
107
+ export function createWidgetAction(entityName, presetName, conventions, templateDir) {
105
108
  const widgetSuffix = conventions.widgetSliceSuffix ?? 'Table';
106
109
  return {
107
110
  type: ACTION_TYPES.COMPONENT,
108
111
  layer: FSD_LAYERS.WIDGET,
109
112
  slice: `${entityName}${widgetSuffix}`,
110
113
  name: `${entityName}${widgetSuffix}`,
111
- template: `${PRESET_DIRS.ROOT}/${presetName}/${FSD_LAYERS.WIDGET}/${PRESET_DIRS.TABLE}`
114
+ template: `${PRESET_DIRS.ROOT}/${presetName}/${FSD_LAYERS.WIDGET}/${templateDir}`
112
115
  };
113
116
  }
114
117
  /**
115
118
  * Create component action for page
116
119
  */
117
- export function createPageAction(entityName, presetName, conventions) {
120
+ /**
121
+ * Create component action for page
122
+ */
123
+ export function createPageAction(entityName, presetName, conventions, templateDir) {
118
124
  const pageSuffix = conventions.pageSliceSuffix ?? 'Page';
119
125
  return {
120
126
  type: ACTION_TYPES.COMPONENT,
121
127
  layer: FSD_LAYERS.PAGE,
122
128
  slice: `${entityName}${pageSuffix}`,
123
129
  name: `${entityName}${pageSuffix}`,
124
- template: `${PRESET_DIRS.ROOT}/${presetName}/${FSD_LAYERS.PAGE}/${FSD_LAYERS.PAGE}`
130
+ template: `${PRESET_DIRS.ROOT}/${presetName}/${FSD_LAYERS.PAGE}/${templateDir}`
125
131
  };
126
132
  }
127
133
  /**
@@ -166,17 +172,19 @@ export async function discoverTemplates(presetDir, presetName, entityName, conve
166
172
  }
167
173
  }
168
174
  // Feature layer
169
- else if (layer === FSD_LAYERS.FEATURE && entry.name === PRESET_DIRS.BUTTONS) {
170
- const buttonActions = await createFeatureButtonActions(fullPath, entityName, presetName, conventions);
171
- actions.push(...buttonActions);
175
+ // Feature layer
176
+ else if (layer === FSD_LAYERS.FEATURE) {
177
+ // Treat any directory in feature layer as a potential specific feature group
178
+ const featureActions = await createFeatureActions(fullPath, entityName, presetName, conventions);
179
+ actions.push(...featureActions);
172
180
  }
173
181
  // Widget layer
174
- else if (layer === FSD_LAYERS.WIDGET && entry.name === PRESET_DIRS.TABLE) {
175
- actions.push(createWidgetTableAction(entityName, presetName, conventions));
182
+ else if (layer === FSD_LAYERS.WIDGET) {
183
+ actions.push(createWidgetAction(entityName, presetName, conventions, entry.name));
176
184
  }
177
185
  // Page layer
178
- else if (layer === FSD_LAYERS.PAGE && entry.name === FSD_LAYERS.PAGE) {
179
- actions.push(createPageAction(entityName, presetName, conventions));
186
+ else if (layer === FSD_LAYERS.PAGE) {
187
+ actions.push(createPageAction(entityName, presetName, conventions, entry.name));
180
188
  }
181
189
  // Shared layer
182
190
  else if (layer === FSD_LAYERS.SHARED) {
@@ -1 +1 @@
1
- {"version":3,"file":"presetLoading.d.ts","sourceRoot":"","sources":["../../../src/lib/preset/presetLoading.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErG;;;GAGG;AACH,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,cAAc,GAAG,IAAI,CAAC,CAanG;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAUpF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAChC,MAAM,EAAE,YAAY,GAAG,cAAc,EACrC,IAAI,EAAE,gBAAgB,GACvB,YAAY,CAKd;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAClC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,YAAY,GACrB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAgB9B"}
1
+ {"version":3,"file":"presetLoading.d.ts","sourceRoot":"","sources":["../../../src/lib/preset/presetLoading.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErG;;;GAGG;AACH,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,cAAc,GAAG,IAAI,CAAC,CAcnG;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAUpF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAChC,MAAM,EAAE,YAAY,GAAG,cAAc,EACrC,IAAI,EAAE,gBAAgB,GACvB,YAAY,CAKd;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAClC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,YAAY,GACrB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAgB9B"}
@@ -20,7 +20,8 @@ export async function loadPresetTs(presetDir) {
20
20
  const imported = await jiti.import(presetTsPath, { default: true });
21
21
  return imported.default || imported;
22
22
  }
23
- catch {
23
+ catch (error) {
24
+ console.error(`Failed to load preset content from ${presetTsPath}:`, error);
24
25
  return null;
25
26
  }
26
27
  }
@@ -1,3 +1,4 @@
1
+ import { TemplateContext } from '../../config/types.js';
1
2
  /**
2
3
  * Resolve the list of directories to search for templates
3
4
  * Custom directory is checked first, then internal templates
@@ -10,22 +11,23 @@ export declare function resolveTemplateDirs(customTemplatesDir?: string): string
10
11
  export declare function findTemplateDir(layer: string, type: string, searchDirs: string[]): Promise<string | null>;
11
12
  /**
12
13
  * Read the component template file
14
+ * Supports .tsx, .ts, .js for dynamic templates, falls back to static string reading
13
15
  */
14
- export declare function readComponentTemplate(templateDir: string): Promise<string>;
16
+ export declare function readComponentTemplate(templateDir: string): Promise<string | ((context: TemplateContext) => string)>;
15
17
  /**
16
18
  * Read the styles template file (optional)
17
19
  * @returns The styles content or empty string if not found
18
20
  */
19
- export declare function readStylesTemplate(templateDir: string): Promise<string>;
21
+ export declare function readStylesTemplate(templateDir: string): Promise<string | ((context: TemplateContext) => string)>;
20
22
  /**
21
23
  * Load a template for a given layer and type
22
24
  * Orchestrates finding and reading template files
23
25
  */
24
26
  export declare function loadTemplate(layer: string, type?: string, customTemplatesDir?: string): Promise<{
25
- component: string;
26
- styles: string;
27
+ component: string | ((context: TemplateContext) => string);
28
+ styles: string | ((context: TemplateContext) => string);
27
29
  }>;
28
- export declare function processTemplate(content: string, variables: Record<string, any>): string;
30
+ export declare function processTemplate(content: string | ((context: TemplateContext) => string), variables: Record<string, any> | TemplateContext): string;
29
31
  export declare function listPresets(customTemplatesDir?: string): Promise<string[]>;
30
32
  export declare function resolvePresetDir(presetName: string, customTemplatesDir?: string): Promise<string | null>;
31
33
  //# sourceMappingURL=templateLoader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"templateLoader.d.ts","sourceRoot":"","sources":["../../../src/lib/templates/templateLoader.ts"],"names":[],"mappings":"AAcA;;;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;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGhF;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAO7E;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,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAchD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAEvF;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":"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,CAuBzH;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,15 +1,11 @@
1
- /**
2
- * Template loading and processing.
3
- *
4
- * Responsible for finding, reading, and processing template files. Handles variable
5
- * substitution in template content and resolves template paths based on layers and types.
6
- */
7
1
  import { readFile, readdir, stat } from 'fs/promises';
8
- import { join, dirname } from 'path';
2
+ import { join, dirname, resolve } from 'path';
9
3
  import { fileURLToPath } from 'url';
4
+ import { createJiti } from 'jiti';
10
5
  import { TEMPLATE_FILES, PRESET_DIRS } from '../constants.js';
11
6
  const __filename = fileURLToPath(import.meta.url);
12
7
  const __dirname = dirname(__filename);
8
+ const jiti = createJiti(__filename);
13
9
  /**
14
10
  * Resolve the list of directories to search for templates
15
11
  * Custom directory is checked first, then internal templates
@@ -42,8 +38,28 @@ export async function findTemplateDir(layer, type, searchDirs) {
42
38
  }
43
39
  /**
44
40
  * Read the component template file
41
+ * Supports .tsx, .ts, .js for dynamic templates, falls back to static string reading
45
42
  */
46
43
  export async function readComponentTemplate(templateDir) {
44
+ // Try to find dynamic template files
45
+ const extensions = ['.tsx', '.ts', '.js'];
46
+ const baseName = TEMPLATE_FILES.COMPONENT.replace('.tsx', ''); // Removing extension if present in constant to try different ones
47
+ // Check for Component.tsx, Component.ts, Component.js that are modules
48
+ for (const ext of extensions) {
49
+ try {
50
+ const modulePath = resolve(templateDir, `Component${ext}`);
51
+ await stat(modulePath);
52
+ // Found a module file, load it with jiti
53
+ const module = await jiti.import(modulePath);
54
+ if (typeof module.default === 'function') {
55
+ return module.default;
56
+ }
57
+ }
58
+ catch {
59
+ // Continue
60
+ }
61
+ }
62
+ // Fallback to reading as text (original behavior)
47
63
  const componentPath = join(templateDir, TEMPLATE_FILES.COMPONENT);
48
64
  return await readFile(componentPath, 'utf-8');
49
65
  }
@@ -52,6 +68,24 @@ export async function readComponentTemplate(templateDir) {
52
68
  * @returns The styles content or empty string if not found
53
69
  */
54
70
  export async function readStylesTemplate(templateDir) {
71
+ // Try to find dynamic template files
72
+ const extensions = ['.ts', '.js'];
73
+ // Check for Component.styles.ts, Component.styles.js
74
+ for (const ext of extensions) {
75
+ try {
76
+ // Adjust this if TEMPLATE_FILES.STYLES differs
77
+ const modulePath = join(templateDir, `Component.styles${ext}`);
78
+ await stat(modulePath);
79
+ // Found a module file, load it with jiti
80
+ const module = await jiti.import(modulePath);
81
+ if (typeof module.default === 'function') {
82
+ return module.default;
83
+ }
84
+ }
85
+ catch {
86
+ // Continue
87
+ }
88
+ }
55
89
  const stylesPath = join(templateDir, TEMPLATE_FILES.STYLES);
56
90
  try {
57
91
  return await readFile(stylesPath, 'utf-8');
@@ -77,6 +111,9 @@ export async function loadTemplate(layer, type = 'ui', customTemplatesDir) {
77
111
  return { component, styles };
78
112
  }
79
113
  export function processTemplate(content, variables) {
114
+ if (typeof content === 'function') {
115
+ return content(variables);
116
+ }
80
117
  return content.replace(/\{\s*\{\s*(\w+)\s*\}\s*\}/g, (_, key) => String(variables[key] ?? ''));
81
118
  }
82
119
  export async function listPresets(customTemplatesDir) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starodubenko/fsd-gen",
3
- "version": "0.1.0",
3
+ "version": "1.0.0-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",
@@ -19,7 +19,7 @@
19
19
  "license": "MIT",
20
20
  "repository": {
21
21
  "type": "git",
22
- "url": "git+https://github.com/rodionstarodubenko/fsd-generator.git"
22
+ "url": "git+https://github.com/Starodubenko/fsd-generator.git"
23
23
  },
24
24
  "keywords": [
25
25
  "fsd",