@starodubenko/fsd-gen 0.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.
Files changed (81) hide show
  1. package/README.md +61 -0
  2. package/cli.js +114 -0
  3. package/dist/config/defineConfig.d.ts +8 -0
  4. package/dist/config/defineConfig.d.ts.map +1 -0
  5. package/dist/config/defineConfig.js +8 -0
  6. package/dist/config/types.d.ts +98 -0
  7. package/dist/config/types.d.ts.map +1 -0
  8. package/dist/config/types.js +9 -0
  9. package/dist/index.d.ts +4 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +3 -0
  12. package/dist/lib/barrels/updateBarrels.d.ts +2 -0
  13. package/dist/lib/barrels/updateBarrels.d.ts.map +1 -0
  14. package/dist/lib/barrels/updateBarrels.js +28 -0
  15. package/dist/lib/config/loadConfig.d.ts +21 -0
  16. package/dist/lib/config/loadConfig.d.ts.map +1 -0
  17. package/dist/lib/config/loadConfig.js +61 -0
  18. package/dist/lib/config/validateConfig.d.ts +13 -0
  19. package/dist/lib/config/validateConfig.d.ts.map +1 -0
  20. package/dist/lib/config/validateConfig.js +19 -0
  21. package/dist/lib/constants.d.ts +113 -0
  22. package/dist/lib/constants.d.ts.map +1 -0
  23. package/dist/lib/constants.js +129 -0
  24. package/dist/lib/generators/generate.d.ts +45 -0
  25. package/dist/lib/generators/generate.d.ts.map +1 -0
  26. package/dist/lib/generators/generate.js +104 -0
  27. package/dist/lib/generators/generatePreset.d.ts +6 -0
  28. package/dist/lib/generators/generatePreset.d.ts.map +1 -0
  29. package/dist/lib/generators/generatePreset.js +102 -0
  30. package/dist/lib/helpers/presetHelpers.d.ts +55 -0
  31. package/dist/lib/helpers/presetHelpers.d.ts.map +1 -0
  32. package/dist/lib/helpers/presetHelpers.js +53 -0
  33. package/dist/lib/naming/names.d.ts +10 -0
  34. package/dist/lib/naming/names.d.ts.map +1 -0
  35. package/dist/lib/naming/names.js +15 -0
  36. package/dist/lib/naming/resolvePaths.d.ts +33 -0
  37. package/dist/lib/naming/resolvePaths.d.ts.map +1 -0
  38. package/dist/lib/naming/resolvePaths.js +58 -0
  39. package/dist/lib/preset/actionExecution.d.ts +32 -0
  40. package/dist/lib/preset/actionExecution.d.ts.map +1 -0
  41. package/dist/lib/preset/actionExecution.js +138 -0
  42. package/dist/lib/preset/presetDiscovery.d.ts +40 -0
  43. package/dist/lib/preset/presetDiscovery.d.ts.map +1 -0
  44. package/dist/lib/preset/presetDiscovery.js +189 -0
  45. package/dist/lib/preset/presetLoading.d.ts +22 -0
  46. package/dist/lib/preset/presetLoading.d.ts.map +1 -0
  47. package/dist/lib/preset/presetLoading.js +69 -0
  48. package/dist/lib/routing/injectRoute.d.ts +17 -0
  49. package/dist/lib/routing/injectRoute.d.ts.map +1 -0
  50. package/dist/lib/routing/injectRoute.js +66 -0
  51. package/dist/lib/templates/templateLoader.d.ts +31 -0
  52. package/dist/lib/templates/templateLoader.d.ts.map +1 -0
  53. package/dist/lib/templates/templateLoader.js +122 -0
  54. package/package.json +64 -0
  55. package/templates/entity/model-ui-basic/Component.styles.ts +1 -0
  56. package/templates/entity/model-ui-basic/Component.tsx +18 -0
  57. package/templates/feature/ui-model-basic/Component.styles.ts +1 -0
  58. package/templates/feature/ui-model-basic/Component.tsx +17 -0
  59. package/templates/page/ui-basic/Component.styles.ts +1 -0
  60. package/templates/page/ui-basic/Component.tsx +17 -0
  61. package/templates/preset/table/entity/api/create/Component.tsx +19 -0
  62. package/templates/preset/table/entity/api/delete/Component.tsx +17 -0
  63. package/templates/preset/table/entity/api/get/Component.tsx +34 -0
  64. package/templates/preset/table/entity/api/update/Component.tsx +18 -0
  65. package/templates/preset/table/entity/model.ts +9 -0
  66. package/templates/preset/table/entity/ui/Component.tsx +11 -0
  67. package/templates/preset/table/feature/buttons/create/Component.styles.ts +1 -0
  68. package/templates/preset/table/feature/buttons/create/Component.tsx +30 -0
  69. package/templates/preset/table/feature/buttons/delete/Component.styles.ts +1 -0
  70. package/templates/preset/table/feature/buttons/delete/Component.tsx +30 -0
  71. package/templates/preset/table/feature/buttons/edit/Component.styles.ts +1 -0
  72. package/templates/preset/table/feature/buttons/edit/Component.tsx +30 -0
  73. package/templates/preset/table/feature/buttons.tsx +11 -0
  74. package/templates/preset/table/page/page/Component.tsx +19 -0
  75. package/templates/preset/table/page/page.tsx +19 -0
  76. package/templates/preset/table/widget/table/Component.tsx +50 -0
  77. package/templates/preset/table/widget/table.tsx +50 -0
  78. package/templates/shared/ui-basic/Component.styles.ts +1 -0
  79. package/templates/shared/ui-basic/Component.tsx +18 -0
  80. package/templates/widget/ui-basic/Component.styles.ts +1 -0
  81. package/templates/widget/ui-basic/Component.tsx +18 -0
@@ -0,0 +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"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Action execution engine for presets.
3
+ *
4
+ * Responsible for executing the individual actions defined in a preset (e.g., creating files,
5
+ * updating barrels, generating components). Handles variable substitution and logic execution.
6
+ */
7
+ import { join, resolve, dirname, basename } from 'path';
8
+ import { writeFile, mkdir, readFile } from 'fs/promises';
9
+ import { fileURLToPath } from 'url';
10
+ import { updateBarrel } from '../barrels/updateBarrels.js';
11
+ import { ACTION_TYPES, FILE_EXTENSIONS } from '../constants.js';
12
+ import { generateComponent, generateHook, generateStyles } from '../generators/generate.js';
13
+ import { resolveFsdPaths } from '../naming/resolvePaths.js';
14
+ import { processTemplate } from '../templates/templateLoader.js';
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+ /**
18
+ * Prepare variables for an action by merging global and action-specific variables
19
+ */
20
+ export function prepareActionVariables(action, name, globalVars) {
21
+ return {
22
+ name,
23
+ componentName: name,
24
+ ...globalVars,
25
+ ...action.variables
26
+ };
27
+ }
28
+ /**
29
+ * Execute a component action (generate a component)
30
+ */
31
+ export async function executeComponentAction(action, variables, config) {
32
+ const componentName = processTemplate(action.name || action.slice, variables);
33
+ const sliceName = processTemplate(action.slice, variables);
34
+ const paths = resolveFsdPaths(config.rootDir, action.layer, sliceName, componentName);
35
+ const context = {
36
+ ...variables,
37
+ componentName,
38
+ sliceName,
39
+ layer: action.layer,
40
+ };
41
+ await generateComponent(paths, context, action.template, config.templatesDir);
42
+ }
43
+ /**
44
+ * Execute a hook action
45
+ */
46
+ export async function executeHookAction(action, // Using any for now to avoid complexity with union types in loop
47
+ variables, config) {
48
+ const componentName = processTemplate(action.name || action.slice, variables);
49
+ const sliceName = processTemplate(action.slice, variables);
50
+ const paths = resolveFsdPaths(config.rootDir, action.layer, sliceName, componentName);
51
+ const context = {
52
+ ...variables,
53
+ componentName,
54
+ sliceName,
55
+ layer: action.layer,
56
+ };
57
+ await generateHook(paths, context, action.template, config.templatesDir);
58
+ }
59
+ /**
60
+ * Execute a styles action
61
+ */
62
+ export async function executeStylesAction(action, variables, config) {
63
+ const componentName = processTemplate(action.name || action.slice, variables);
64
+ const sliceName = processTemplate(action.slice, variables);
65
+ const paths = resolveFsdPaths(config.rootDir, action.layer, sliceName, componentName);
66
+ const context = {
67
+ ...variables,
68
+ componentName,
69
+ sliceName,
70
+ layer: action.layer,
71
+ };
72
+ await generateStyles(paths, context, action.template, config.templatesDir);
73
+ }
74
+ /**
75
+ * Load a file template from the templates directory
76
+ */
77
+ export async function loadFileTemplate(templatePath, customTemplatesDir) {
78
+ const internalTemplatesDir = join(__dirname, '../../../templates');
79
+ const pathsToCheck = [];
80
+ if (customTemplatesDir) {
81
+ pathsToCheck.push(join(resolve(process.cwd(), customTemplatesDir), templatePath));
82
+ }
83
+ pathsToCheck.push(join(internalTemplatesDir, templatePath));
84
+ for (const p of pathsToCheck) {
85
+ try {
86
+ const content = await readFile(p, 'utf-8');
87
+ console.log(`Loaded file template from: ${p}`);
88
+ return content;
89
+ }
90
+ catch {
91
+ // Try next path
92
+ }
93
+ }
94
+ throw new Error(`Could not find file template: ${templatePath}`);
95
+ }
96
+ /**
97
+ * Execute a file action (create a file from template)
98
+ */
99
+ export async function executeFileAction(action, variables, config) {
100
+ const targetPath = join(process.cwd(), config.rootDir, processTemplate(action.path, variables));
101
+ await mkdir(dirname(targetPath), { recursive: true });
102
+ if (action.template) {
103
+ const content = await loadFileTemplate(action.template, config.templatesDir);
104
+ const processed = processTemplate(content, variables);
105
+ await writeFile(targetPath, processed);
106
+ console.log(`Created ${targetPath}`);
107
+ // Update barrel for the file (only for TypeScript files)
108
+ if (targetPath.endsWith(FILE_EXTENSIONS.TYPESCRIPT) || targetPath.endsWith(FILE_EXTENSIONS.TSX)) {
109
+ const dir = dirname(targetPath);
110
+ const fileName = basename(targetPath, FILE_EXTENSIONS.TYPESCRIPT); // Remove .ts for export
111
+ updateBarrel(dir, fileName, fileName);
112
+ }
113
+ }
114
+ }
115
+ /**
116
+ * Execute all preset actions
117
+ * Orchestrates executing component and file actions
118
+ */
119
+ export async function executeActions(actions, name, globalVars, config) {
120
+ for (const action of actions) {
121
+ const variables = prepareActionVariables(action, name, globalVars);
122
+ switch (action.type) {
123
+ case ACTION_TYPES.COMPONENT:
124
+ await executeComponentAction(action, variables, config);
125
+ break;
126
+ case ACTION_TYPES.FILE:
127
+ await executeFileAction(action, variables, config);
128
+ break;
129
+ case ACTION_TYPES.HOOK:
130
+ await executeHookAction(action, variables, config);
131
+ break;
132
+ case ACTION_TYPES.STYLES:
133
+ await executeStylesAction(action, variables, config);
134
+ break;
135
+ }
136
+ }
137
+ console.log('Preset generation complete (declarative).');
138
+ }
@@ -0,0 +1,40 @@
1
+ import type { Dirent } from 'fs';
2
+ import { PresetAction, PresetComponentAction, PresetFileAction, ConventionConfig } from '../../config/types.js';
3
+ /**
4
+ * Scan a layer directory for entries
5
+ */
6
+ export declare function scanLayerDirectory(presetDir: string, layer: string): Promise<Dirent[]>;
7
+ /**
8
+ * Create a file action from a .ts file entry
9
+ */
10
+ export declare function createFileAction(entry: Dirent, layer: string, entityName: string, presetName: string): PresetFileAction;
11
+ /**
12
+ * Create component action for entity UI
13
+ */
14
+ export declare function createEntityUiAction(entityName: string, presetName: string): PresetComponentAction;
15
+ /**
16
+ * Create component actions for entity API hooks
17
+ */
18
+ export declare function createEntityApiActions(apiDir: string, entityName: string, presetName: string): Promise<PresetAction[]>;
19
+ /**
20
+ * Create component actions for feature buttons
21
+ */
22
+ export declare function createFeatureButtonActions(buttonsDir: string, entityName: string, presetName: string, conventions: ConventionConfig): Promise<PresetAction[]>;
23
+ /**
24
+ * Create component action for widget table
25
+ */
26
+ export declare function createWidgetTableAction(entityName: string, presetName: string, conventions: ConventionConfig): PresetComponentAction;
27
+ /**
28
+ * Create component action for page
29
+ */
30
+ export declare function createPageAction(entityName: string, presetName: string, conventions: ConventionConfig): PresetComponentAction;
31
+ /**
32
+ * Create component action for shared
33
+ */
34
+ export declare function createSharedAction(entryName: string, entityName: string, presetName: string): PresetComponentAction;
35
+ /**
36
+ * Auto-discover templates in a preset directory based on conventions
37
+ * Orchestrates scanning all layers and creating appropriate actions
38
+ */
39
+ export declare function discoverTemplates(presetDir: string, presetName: string, entityName: string, conventions?: ConventionConfig): Promise<PresetAction[]>;
40
+ //# sourceMappingURL=presetDiscovery.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Preset discovery mechanism.
3
+ *
4
+ * Scans the preset directory structure to automatically discover and register template actions.
5
+ * Converts the file system structure of a preset into a list of executable generator actions.
6
+ */
7
+ /**
8
+ * Preset discovery mechanism.
9
+ *
10
+ * Scans the preset directory structure to automatically discover and register template actions.
11
+ * Converts the file system structure of a preset into a list of executable generator actions.
12
+ */
13
+ import { readdir, stat } from 'fs/promises';
14
+ import { join } from 'path';
15
+ import { LAYER_PLURALS, FSD_LAYERS, FSD_SEGMENTS, FILE_EXTENSIONS, PRESET_DIRS, API_HOOK_PREFIXES, API_OPERATIONS, ACTION_TYPES } from '../constants.js';
16
+ /**
17
+ * Scan a layer directory for entries
18
+ */
19
+ export async function scanLayerDirectory(presetDir, layer) {
20
+ const layerDir = join(presetDir, layer);
21
+ try {
22
+ const layerStat = await stat(layerDir);
23
+ if (!layerStat.isDirectory())
24
+ return [];
25
+ return await readdir(layerDir, { withFileTypes: true });
26
+ }
27
+ catch {
28
+ return [];
29
+ }
30
+ }
31
+ /**
32
+ * Create a file action from a .ts file entry
33
+ */
34
+ export function createFileAction(entry, layer, entityName, presetName) {
35
+ const baseName = entry.name.replace(FILE_EXTENSIONS.TYPESCRIPT, '');
36
+ const layerPlural = LAYER_PLURALS[layer] || 'pages';
37
+ return {
38
+ type: ACTION_TYPES.FILE,
39
+ path: `${layerPlural}/${entityName}/${FSD_SEGMENTS.MODEL}/${baseName}${FILE_EXTENSIONS.TYPESCRIPT}`,
40
+ template: `${PRESET_DIRS.ROOT}/${presetName}/${layer}/${entry.name}`
41
+ };
42
+ }
43
+ /**
44
+ * Create component action for entity UI
45
+ */
46
+ export function createEntityUiAction(entityName, presetName) {
47
+ return {
48
+ type: ACTION_TYPES.COMPONENT,
49
+ layer: FSD_LAYERS.ENTITY,
50
+ slice: entityName,
51
+ name: entityName,
52
+ template: `${PRESET_DIRS.ROOT}/${presetName}/${FSD_LAYERS.ENTITY}/${FSD_SEGMENTS.UI}`
53
+ };
54
+ }
55
+ /**
56
+ * Create component actions for entity API hooks
57
+ */
58
+ export async function createEntityApiActions(apiDir, entityName, presetName) {
59
+ const actions = [];
60
+ const apiEntries = await readdir(apiDir, { withFileTypes: true });
61
+ for (const apiEntry of apiEntries) {
62
+ if (apiEntry.isDirectory()) {
63
+ const hookName = apiEntry.name;
64
+ const hookPrefix = API_HOOK_PREFIXES[hookName];
65
+ const name = hookPrefix
66
+ ? `${hookPrefix}${entityName}${hookName === API_OPERATIONS.GET ? 's' : ''}`
67
+ : `use${hookName.charAt(0).toUpperCase() + hookName.slice(1)}${entityName}`;
68
+ actions.push({
69
+ type: ACTION_TYPES.HOOK,
70
+ layer: FSD_LAYERS.ENTITY,
71
+ slice: entityName,
72
+ name,
73
+ template: `${PRESET_DIRS.ROOT}/${presetName}/${FSD_LAYERS.ENTITY}/${FSD_SEGMENTS.API}/${apiEntry.name}`
74
+ });
75
+ }
76
+ }
77
+ return actions;
78
+ }
79
+ /**
80
+ * Create component actions for feature buttons
81
+ */
82
+ export async function createFeatureButtonActions(buttonsDir, entityName, presetName, conventions) {
83
+ const actions = [];
84
+ 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);
90
+ actions.push({
91
+ type: ACTION_TYPES.COMPONENT,
92
+ layer: FSD_LAYERS.FEATURE,
93
+ slice: `${featurePrefix}${entityName}`,
94
+ name: `${capitalizedType}${entityName}Button`,
95
+ template: `${PRESET_DIRS.ROOT}/${presetName}/${FSD_LAYERS.FEATURE}/${PRESET_DIRS.BUTTONS}/${buttonEntry.name}`
96
+ });
97
+ }
98
+ }
99
+ return actions;
100
+ }
101
+ /**
102
+ * Create component action for widget table
103
+ */
104
+ export function createWidgetTableAction(entityName, presetName, conventions) {
105
+ const widgetSuffix = conventions.widgetSliceSuffix ?? 'Table';
106
+ return {
107
+ type: ACTION_TYPES.COMPONENT,
108
+ layer: FSD_LAYERS.WIDGET,
109
+ slice: `${entityName}${widgetSuffix}`,
110
+ name: `${entityName}${widgetSuffix}`,
111
+ template: `${PRESET_DIRS.ROOT}/${presetName}/${FSD_LAYERS.WIDGET}/${PRESET_DIRS.TABLE}`
112
+ };
113
+ }
114
+ /**
115
+ * Create component action for page
116
+ */
117
+ export function createPageAction(entityName, presetName, conventions) {
118
+ const pageSuffix = conventions.pageSliceSuffix ?? 'Page';
119
+ return {
120
+ type: ACTION_TYPES.COMPONENT,
121
+ layer: FSD_LAYERS.PAGE,
122
+ slice: `${entityName}${pageSuffix}`,
123
+ name: `${entityName}${pageSuffix}`,
124
+ template: `${PRESET_DIRS.ROOT}/${presetName}/${FSD_LAYERS.PAGE}/${FSD_LAYERS.PAGE}`
125
+ };
126
+ }
127
+ /**
128
+ * Create component action for shared
129
+ */
130
+ export function createSharedAction(entryName, entityName, presetName) {
131
+ // Shared components often don't follow entity naming directly,
132
+ // but in discovery mode we treat existing dirs as components.
133
+ return {
134
+ type: ACTION_TYPES.COMPONENT,
135
+ layer: FSD_LAYERS.SHARED,
136
+ slice: entryName === FSD_LAYERS.SHARED ? entityName : entryName,
137
+ name: entryName === FSD_LAYERS.SHARED ? entityName : entryName,
138
+ template: `${PRESET_DIRS.ROOT}/${presetName}/${FSD_LAYERS.SHARED}/${entryName}`
139
+ };
140
+ }
141
+ /**
142
+ * Auto-discover templates in a preset directory based on conventions
143
+ * Orchestrates scanning all layers and creating appropriate actions
144
+ */
145
+ export async function discoverTemplates(presetDir, presetName, entityName, conventions = {}) {
146
+ const actions = [];
147
+ const layers = ['entity', 'feature', 'widget', 'page', 'shared'];
148
+ for (const layer of layers) {
149
+ const entries = await scanLayerDirectory(presetDir, layer);
150
+ for (const entry of entries) {
151
+ const fullPath = join(presetDir, layer, entry.name);
152
+ // Check for .ts files (file actions)
153
+ if (entry.isFile() && entry.name.endsWith(FILE_EXTENSIONS.TYPESCRIPT)) {
154
+ actions.push(createFileAction(entry, layer, entityName, presetName));
155
+ }
156
+ // Check for directories (component actions)
157
+ if (entry.isDirectory()) {
158
+ // Entity layer
159
+ if (layer === FSD_LAYERS.ENTITY) {
160
+ if (entry.name === FSD_SEGMENTS.UI) {
161
+ actions.push(createEntityUiAction(entityName, presetName));
162
+ }
163
+ else if (entry.name === FSD_SEGMENTS.API) {
164
+ const apiActions = await createEntityApiActions(fullPath, entityName, presetName);
165
+ actions.push(...apiActions);
166
+ }
167
+ }
168
+ // 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);
172
+ }
173
+ // Widget layer
174
+ else if (layer === FSD_LAYERS.WIDGET && entry.name === PRESET_DIRS.TABLE) {
175
+ actions.push(createWidgetTableAction(entityName, presetName, conventions));
176
+ }
177
+ // Page layer
178
+ else if (layer === FSD_LAYERS.PAGE && entry.name === FSD_LAYERS.PAGE) {
179
+ actions.push(createPageAction(entityName, presetName, conventions));
180
+ }
181
+ // Shared layer
182
+ else if (layer === FSD_LAYERS.SHARED) {
183
+ actions.push(createSharedAction(entry.name, entityName, presetName));
184
+ }
185
+ }
186
+ }
187
+ }
188
+ return actions;
189
+ }
@@ -0,0 +1,22 @@
1
+ import { PresetConfig, PresetConfigArgs, PresetConfigFn, FsdGenConfig } from '../../config/types.js';
2
+ /**
3
+ * Load preset configuration from preset.ts file
4
+ * @returns The preset config or null if not found
5
+ */
6
+ export declare function loadPresetTs(presetDir: string): Promise<PresetConfig | PresetConfigFn | null>;
7
+ /**
8
+ * Load preset configuration from preset.json file
9
+ * @returns The preset config or null if not found
10
+ */
11
+ export declare function loadPresetJson(presetDir: string): Promise<PresetConfig | null>;
12
+ /**
13
+ * Evaluate preset config if it's a function
14
+ */
15
+ export declare function evaluatePresetConfig(config: PresetConfig | PresetConfigFn, args: PresetConfigArgs): PresetConfig;
16
+ /**
17
+ * Load preset configuration from a preset directory
18
+ * Tries preset.ts first, then preset.json
19
+ * Orchestrates the entire preset config loading process
20
+ */
21
+ export declare function loadPresetConfig(presetDir: string, name: string, config: FsdGenConfig): Promise<PresetConfig | null>;
22
+ //# sourceMappingURL=presetLoading.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Preset loading logic.
3
+ *
4
+ * Responsible for loading preset configurations from either TypeScript files (preset.ts)
5
+ * or JSON files (preset.json). Handles module resolution and parsing.
6
+ */
7
+ import { join } from 'path';
8
+ import { readFile, stat } from 'fs/promises';
9
+ import { createJiti } from 'jiti';
10
+ /**
11
+ * Load preset configuration from preset.ts file
12
+ * @returns The preset config or null if not found
13
+ */
14
+ export async function loadPresetTs(presetDir) {
15
+ const presetTsPath = join(presetDir, 'preset.ts');
16
+ try {
17
+ await stat(presetTsPath);
18
+ console.log(`Loading preset configuration from ${presetTsPath}...`);
19
+ const jiti = createJiti(import.meta.url);
20
+ const imported = await jiti.import(presetTsPath, { default: true });
21
+ return imported.default || imported;
22
+ }
23
+ catch {
24
+ return null;
25
+ }
26
+ }
27
+ /**
28
+ * Load preset configuration from preset.json file
29
+ * @returns The preset config or null if not found
30
+ */
31
+ export async function loadPresetJson(presetDir) {
32
+ const presetJsonPath = join(presetDir, 'preset.json');
33
+ try {
34
+ const content = await readFile(presetJsonPath, 'utf-8');
35
+ console.log(`Found preset.json at ${presetJsonPath}...`);
36
+ return JSON.parse(content);
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
42
+ /**
43
+ * Evaluate preset config if it's a function
44
+ */
45
+ export function evaluatePresetConfig(config, args) {
46
+ if (typeof config === 'function') {
47
+ return config(args);
48
+ }
49
+ return config;
50
+ }
51
+ /**
52
+ * Load preset configuration from a preset directory
53
+ * Tries preset.ts first, then preset.json
54
+ * Orchestrates the entire preset config loading process
55
+ */
56
+ export async function loadPresetConfig(presetDir, name, config) {
57
+ // Try preset.ts first
58
+ let presetConfig = await loadPresetTs(presetDir);
59
+ // Fall back to preset.json
60
+ if (!presetConfig) {
61
+ presetConfig = await loadPresetJson(presetDir);
62
+ }
63
+ if (!presetConfig) {
64
+ return null;
65
+ }
66
+ // Evaluate if function
67
+ const args = { name, config };
68
+ return evaluatePresetConfig(presetConfig, args);
69
+ }
@@ -0,0 +1,17 @@
1
+ export interface RouteInjectionOptions {
2
+ /** Root directory where App.tsx is located */
3
+ rootDir: string;
4
+ /** Route path (e.g., '/test') */
5
+ path: string;
6
+ /** Import path for the page component (e.g., '@pages/TestPage') */
7
+ importPath: string;
8
+ /** Component name (e.g., 'TestPage') */
9
+ componentName: string;
10
+ /** Target file for route injection (e.g., 'App.tsx') @default "App.tsx" */
11
+ appFile?: string;
12
+ }
13
+ /**
14
+ * Inject a route into App.tsx
15
+ */
16
+ export declare function injectRoute(options: RouteInjectionOptions): Promise<void>;
17
+ //# sourceMappingURL=injectRoute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injectRoute.d.ts","sourceRoot":"","sources":["../../../src/lib/routing/injectRoute.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,qBAAqB;IAClC,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+D/E"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Route injection logic.
3
+ *
4
+ * Handles the automatic injection of routes into the main application entry point (e.g. App.tsx)
5
+ * when a page is generated. Parses the AST to find the correct insertion point.
6
+ */
7
+ import { join } from 'path';
8
+ import { ROUTING } from '../constants.js';
9
+ import { readFile, writeFile } from 'fs/promises';
10
+ /**
11
+ * Inject a route into App.tsx
12
+ */
13
+ export async function injectRoute(options) {
14
+ const { rootDir, path, importPath, componentName, appFile = ROUTING.APP_FILE } = options;
15
+ const appFilePath = join(rootDir, appFile);
16
+ try {
17
+ // Read App.tsx
18
+ let content = await readFile(appFilePath, 'utf-8');
19
+ // Check if route injection point exists
20
+ if (!content.includes(ROUTING.MARKER)) {
21
+ console.warn(`⚠️ Warning: ${ROUTING.MARKER} comment not found in ${ROUTING.APP_FILE}`);
22
+ console.warn(' Route was not injected automatically.');
23
+ return;
24
+ }
25
+ // Generate import statement
26
+ const importStatement = `import { ${componentName} } from '${importPath}';\n`;
27
+ // Check if import already exists
28
+ if (content.includes(importStatement.trim())) {
29
+ console.log(`ℹ️ Import for ${componentName} already exists, skipping...`);
30
+ }
31
+ else {
32
+ // Find where to insert import (after last import statement)
33
+ const lines = content.split('\n');
34
+ let lastImportIndex = -1;
35
+ for (let i = 0; i < lines.length; i++) {
36
+ if (lines[i].match(/^import\s/)) {
37
+ lastImportIndex = i;
38
+ }
39
+ }
40
+ if (lastImportIndex >= 0) {
41
+ lines.splice(lastImportIndex + 1, 0, importStatement.trimEnd());
42
+ content = lines.join('\n');
43
+ }
44
+ }
45
+ // Generate route element
46
+ const routeElement = ` <Route path="${path}" element={<${componentName} />} />`;
47
+ // Check if route already exists
48
+ if (content.includes(`path="${path}"`)) {
49
+ console.log(`ℹ️ Route for path "${path}" already exists, skipping...`);
50
+ return;
51
+ }
52
+ // Insert route at injection point
53
+ content = content.replace(ROUTING.MARKER, `${routeElement}\n ${ROUTING.MARKER}`);
54
+ // Write back to file
55
+ await writeFile(appFilePath, content, 'utf-8');
56
+ console.log(`✅ Injected route "${path}" -> ${componentName}`);
57
+ }
58
+ catch (error) {
59
+ if (error.code === 'ENOENT') {
60
+ console.warn('⚠️ Warning: App.tsx not found, route injection skipped');
61
+ }
62
+ else {
63
+ throw error;
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Resolve the list of directories to search for templates
3
+ * Custom directory is checked first, then internal templates
4
+ */
5
+ export declare function resolveTemplateDirs(customTemplatesDir?: string): string[];
6
+ /**
7
+ * Find the template directory in the search directories
8
+ * @returns The path to the template directory or null if not found
9
+ */
10
+ export declare function findTemplateDir(layer: string, type: string, searchDirs: string[]): Promise<string | null>;
11
+ /**
12
+ * Read the component template file
13
+ */
14
+ export declare function readComponentTemplate(templateDir: string): Promise<string>;
15
+ /**
16
+ * Read the styles template file (optional)
17
+ * @returns The styles content or empty string if not found
18
+ */
19
+ export declare function readStylesTemplate(templateDir: string): Promise<string>;
20
+ /**
21
+ * Load a template for a given layer and type
22
+ * Orchestrates finding and reading template files
23
+ */
24
+ export declare function loadTemplate(layer: string, type?: string, customTemplatesDir?: string): Promise<{
25
+ component: string;
26
+ styles: string;
27
+ }>;
28
+ export declare function processTemplate(content: string, variables: Record<string, any>): string;
29
+ export declare function listPresets(customTemplatesDir?: string): Promise<string[]>;
30
+ export declare function resolvePresetDir(presetName: string, customTemplatesDir?: string): Promise<string | null>;
31
+ //# sourceMappingURL=templateLoader.d.ts.map
@@ -0,0 +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"}