@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.
- package/README.md +61 -0
- package/cli.js +114 -0
- package/dist/config/defineConfig.d.ts +8 -0
- package/dist/config/defineConfig.d.ts.map +1 -0
- package/dist/config/defineConfig.js +8 -0
- package/dist/config/types.d.ts +98 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +9 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/lib/barrels/updateBarrels.d.ts +2 -0
- package/dist/lib/barrels/updateBarrels.d.ts.map +1 -0
- package/dist/lib/barrels/updateBarrels.js +28 -0
- package/dist/lib/config/loadConfig.d.ts +21 -0
- package/dist/lib/config/loadConfig.d.ts.map +1 -0
- package/dist/lib/config/loadConfig.js +61 -0
- package/dist/lib/config/validateConfig.d.ts +13 -0
- package/dist/lib/config/validateConfig.d.ts.map +1 -0
- package/dist/lib/config/validateConfig.js +19 -0
- package/dist/lib/constants.d.ts +113 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +129 -0
- package/dist/lib/generators/generate.d.ts +45 -0
- package/dist/lib/generators/generate.d.ts.map +1 -0
- package/dist/lib/generators/generate.js +104 -0
- package/dist/lib/generators/generatePreset.d.ts +6 -0
- package/dist/lib/generators/generatePreset.d.ts.map +1 -0
- package/dist/lib/generators/generatePreset.js +102 -0
- package/dist/lib/helpers/presetHelpers.d.ts +55 -0
- package/dist/lib/helpers/presetHelpers.d.ts.map +1 -0
- package/dist/lib/helpers/presetHelpers.js +53 -0
- package/dist/lib/naming/names.d.ts +10 -0
- package/dist/lib/naming/names.d.ts.map +1 -0
- package/dist/lib/naming/names.js +15 -0
- package/dist/lib/naming/resolvePaths.d.ts +33 -0
- package/dist/lib/naming/resolvePaths.d.ts.map +1 -0
- package/dist/lib/naming/resolvePaths.js +58 -0
- package/dist/lib/preset/actionExecution.d.ts +32 -0
- package/dist/lib/preset/actionExecution.d.ts.map +1 -0
- package/dist/lib/preset/actionExecution.js +138 -0
- package/dist/lib/preset/presetDiscovery.d.ts +40 -0
- package/dist/lib/preset/presetDiscovery.d.ts.map +1 -0
- package/dist/lib/preset/presetDiscovery.js +189 -0
- package/dist/lib/preset/presetLoading.d.ts +22 -0
- package/dist/lib/preset/presetLoading.d.ts.map +1 -0
- package/dist/lib/preset/presetLoading.js +69 -0
- package/dist/lib/routing/injectRoute.d.ts +17 -0
- package/dist/lib/routing/injectRoute.d.ts.map +1 -0
- package/dist/lib/routing/injectRoute.js +66 -0
- package/dist/lib/templates/templateLoader.d.ts +31 -0
- package/dist/lib/templates/templateLoader.d.ts.map +1 -0
- package/dist/lib/templates/templateLoader.js +122 -0
- package/package.json +64 -0
- package/templates/entity/model-ui-basic/Component.styles.ts +1 -0
- package/templates/entity/model-ui-basic/Component.tsx +18 -0
- package/templates/feature/ui-model-basic/Component.styles.ts +1 -0
- package/templates/feature/ui-model-basic/Component.tsx +17 -0
- package/templates/page/ui-basic/Component.styles.ts +1 -0
- package/templates/page/ui-basic/Component.tsx +17 -0
- package/templates/preset/table/entity/api/create/Component.tsx +19 -0
- package/templates/preset/table/entity/api/delete/Component.tsx +17 -0
- package/templates/preset/table/entity/api/get/Component.tsx +34 -0
- package/templates/preset/table/entity/api/update/Component.tsx +18 -0
- package/templates/preset/table/entity/model.ts +9 -0
- package/templates/preset/table/entity/ui/Component.tsx +11 -0
- package/templates/preset/table/feature/buttons/create/Component.styles.ts +1 -0
- package/templates/preset/table/feature/buttons/create/Component.tsx +30 -0
- package/templates/preset/table/feature/buttons/delete/Component.styles.ts +1 -0
- package/templates/preset/table/feature/buttons/delete/Component.tsx +30 -0
- package/templates/preset/table/feature/buttons/edit/Component.styles.ts +1 -0
- package/templates/preset/table/feature/buttons/edit/Component.tsx +30 -0
- package/templates/preset/table/feature/buttons.tsx +11 -0
- package/templates/preset/table/page/page/Component.tsx +19 -0
- package/templates/preset/table/page/page.tsx +19 -0
- package/templates/preset/table/widget/table/Component.tsx +50 -0
- package/templates/preset/table/widget/table.tsx +50 -0
- package/templates/shared/ui-basic/Component.styles.ts +1 -0
- package/templates/shared/ui-basic/Component.tsx +18 -0
- package/templates/widget/ui-basic/Component.styles.ts +1 -0
- 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"}
|