muscle-config 1.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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +430 -0
  3. package/dist/commands/create.command.js +104 -0
  4. package/dist/config/projectConfig.js +1 -0
  5. package/dist/features/architecture.feature.js +49 -0
  6. package/dist/features/css.feature.js +107 -0
  7. package/dist/features/feature.interface.js +1 -0
  8. package/dist/features/git.feature.js +52 -0
  9. package/dist/features/mui.feature.js +138 -0
  10. package/dist/features/prettier.feature.js +73 -0
  11. package/dist/features/tailwind.feature.js +116 -0
  12. package/dist/generators/architecture.generator.js +75 -0
  13. package/dist/generators/css-plain.generator.js +215 -0
  14. package/dist/generators/css.generator.js +45 -0
  15. package/dist/generators/mui.generator.js +176 -0
  16. package/dist/generators/react.generator.js +38 -0
  17. package/dist/generators/tailwind.config.generator.js +17 -0
  18. package/dist/generators/toggle.generator.js +26 -0
  19. package/dist/index.js +11 -0
  20. package/dist/prompts/architecture.prompt.js +131 -0
  21. package/dist/prompts/css.prompt.js +217 -0
  22. package/dist/prompts/directory.prompt.js +16 -0
  23. package/dist/prompts/framework.prompt.js +15 -0
  24. package/dist/prompts/git.prompt.js +23 -0
  25. package/dist/prompts/mui.prompt.js +104 -0
  26. package/dist/prompts/prettier.prompt.js +42 -0
  27. package/dist/prompts/projectName.prompt.js +13 -0
  28. package/dist/prompts/styling.prompt.js +17 -0
  29. package/dist/prompts/tailwind.prompt.js +175 -0
  30. package/dist/templates/react/css/App.jsx +41 -0
  31. package/dist/templates/react/css/App.tsx +41 -0
  32. package/dist/templates/react/mui/App.jsx +42 -0
  33. package/dist/templates/react/mui/App.tsx +42 -0
  34. package/dist/templates/react/tailwind-v4/index.css +20 -0
  35. package/dist/templates/react/tailwind-v4/vite.config.js +7 -0
  36. package/dist/templates/react/tailwind-v4/vite.config.ts +7 -0
  37. package/dist/utils/directory.js +43 -0
  38. package/dist/utils/install.js +7 -0
  39. package/dist/utils/logger.js +10 -0
  40. package/dist/utils/rollback.js +63 -0
  41. package/dist/utils/spinner.js +13 -0
  42. package/dist/utils/welcome.js +46 -0
  43. package/package.json +57 -0
@@ -0,0 +1,107 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { spinner } from "../utils/spinner.js";
5
+ import { logger } from "../utils/logger.js";
6
+ import { generateCssReset, generateCssVariables, generateCssTypography, generateCssIndex, generateCssIndexWithImports, generateCssThemeToggle, } from "../generators/css-plain.generator.js";
7
+ import { saveSnapshot, restoreSnapshots, rollbackFeature, } from "../utils/rollback.js";
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ export function cssFeature(config) {
10
+ return {
11
+ name: "css",
12
+ async run(projectPath) {
13
+ try {
14
+ if (config.mode === "skip") {
15
+ logger.info("Skipping CSS configuration — using Vite default.");
16
+ return;
17
+ }
18
+ /**
19
+ * STEP 1
20
+ * Detect TypeScript or JavaScript
21
+ */
22
+ const tsConfigPath = path.join(projectPath, "vite.config.ts");
23
+ let isTypeScript = false;
24
+ try {
25
+ await fs.access(tsConfigPath);
26
+ isTypeScript = true;
27
+ }
28
+ catch { }
29
+ /**
30
+ * STEP 2
31
+ * Generate CSS files — separate files or single file mode
32
+ */
33
+ if (config.separateFiles) {
34
+ spinner.start("Generating CSS files...");
35
+ const stylesDir = path.join(projectPath, "src/styles");
36
+ await fs.mkdir(stylesDir, { recursive: true });
37
+ if (config.reset) {
38
+ await fs.writeFile(path.join(stylesDir, "reset.css"), generateCssReset());
39
+ }
40
+ await fs.writeFile(path.join(stylesDir, "variables.css"), generateCssVariables(config));
41
+ await fs.writeFile(path.join(stylesDir, "typography.css"), generateCssTypography(config));
42
+ spinner.succeed("CSS files generated!");
43
+ /**
44
+ * STEP 3 (separate files)
45
+ * Overwrite src/index.css with @import statements pointing to styles/
46
+ */
47
+ spinner.start("Updating main CSS entry...");
48
+ const mainCssPath = path.join(projectPath, "src/index.css");
49
+ await saveSnapshot(mainCssPath);
50
+ await fs.writeFile(mainCssPath, generateCssIndexWithImports(config));
51
+ spinner.succeed("Main CSS entry updated!");
52
+ }
53
+ else {
54
+ /**
55
+ * STEP 2 (single file)
56
+ * Overwrite src/index.css with all generated styles inline
57
+ */
58
+ spinner.start("Generating CSS styles...");
59
+ const mainCssPath = path.join(projectPath, "src/index.css");
60
+ await saveSnapshot(mainCssPath);
61
+ await fs.writeFile(mainCssPath, generateCssIndex(config));
62
+ spinner.succeed("CSS styles generated!");
63
+ }
64
+ /**
65
+ * STEP 4
66
+ * Generate ThemeToggle component if class dark mode + toggle selected
67
+ */
68
+ if (config.darkModeToggle) {
69
+ spinner.start("Creating ThemeToggle component...");
70
+ const componentDir = path.join(projectPath, "src/components");
71
+ await fs.mkdir(componentDir, { recursive: true });
72
+ const toggleFile = path.join(componentDir, isTypeScript ? "ThemeToggle.tsx" : "ThemeToggle.jsx");
73
+ await fs.writeFile(toggleFile, generateCssThemeToggle(isTypeScript));
74
+ spinner.succeed("ThemeToggle component created!");
75
+ }
76
+ /**
77
+ * STEP 5
78
+ * Copy demo App template if user selected it
79
+ */
80
+ if (config.demo) {
81
+ spinner.start("Copying CSS demo template...");
82
+ const templateDir = path.join(__dirname, "../templates/react/css");
83
+ const appTemplatePath = path.join(templateDir, isTypeScript ? "App.tsx" : "App.jsx");
84
+ const appTargetPath = path.join(projectPath, isTypeScript ? "src/App.tsx" : "src/App.jsx");
85
+ await fs.copyFile(appTemplatePath, appTargetPath);
86
+ spinner.succeed("Demo template copied!");
87
+ }
88
+ /**
89
+ * FINAL SUCCESS MESSAGE
90
+ */
91
+ logger.success("CSS setup complete!");
92
+ }
93
+ catch (error) {
94
+ spinner.fail("Failed to setup CSS.");
95
+ await restoreSnapshots();
96
+ await rollbackFeature([
97
+ path.join(projectPath, "src/styles"),
98
+ path.join(projectPath, "src/components/ThemeToggle.tsx"),
99
+ path.join(projectPath, "src/components/ThemeToggle.jsx"),
100
+ path.join(projectPath, "src/App.tsx"),
101
+ path.join(projectPath, "src/App.jsx"),
102
+ ]);
103
+ throw error;
104
+ }
105
+ },
106
+ };
107
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,52 @@
1
+ import { execa } from "execa";
2
+ import { spinner } from "../utils/spinner.js";
3
+ import { logger } from "../utils/logger.js";
4
+ import { rollbackFeature } from "../utils/rollback.js";
5
+ import path from "path";
6
+ export function gitFeature(config) {
7
+ return {
8
+ name: "git",
9
+ async run(projectPath) {
10
+ try {
11
+ if (!config.init) {
12
+ logger.info("Skipping Git initialization.");
13
+ return;
14
+ }
15
+ /**
16
+ * STEP 1
17
+ * Initialize a new Git repository in the project folder
18
+ */
19
+ spinner.start("Initializing Git repository...");
20
+ await execa("git", ["init"], { cwd: projectPath });
21
+ spinner.succeed("Git repository initialized!");
22
+ if (!config.initialCommit)
23
+ return;
24
+ /**
25
+ * STEP 2
26
+ * Stage all generated files
27
+ */
28
+ spinner.start("Staging files...");
29
+ await execa("git", ["add", "."], { cwd: projectPath });
30
+ spinner.succeed("Files staged!");
31
+ /**
32
+ * STEP 3
33
+ * Create the initial commit with a hardcoded conventional message
34
+ */
35
+ spinner.start("Creating initial commit...");
36
+ await execa("git", ["commit", "-m", "chore: initial commit"], {
37
+ cwd: projectPath,
38
+ });
39
+ spinner.succeed("Initial commit created!");
40
+ /**
41
+ * FINAL SUCCESS MESSAGE
42
+ */
43
+ logger.success("Git setup complete!");
44
+ }
45
+ catch (error) {
46
+ spinner.fail("Failed to setup Git.");
47
+ await rollbackFeature([path.join(projectPath, ".git")]);
48
+ throw error;
49
+ }
50
+ },
51
+ };
52
+ }
@@ -0,0 +1,138 @@
1
+ import { execa } from "execa";
2
+ import fs from "fs/promises";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ import { spinner } from "../utils/spinner.js";
6
+ import { logger } from "../utils/logger.js";
7
+ import { generateMuiTheme, generateMuiThemeToggle, generateThemeContextProvider, generateAppMuiThemeProvider, generateMuiMain, generateMuiIndexCss, } from "../generators/mui.generator.js";
8
+ import { restoreSnapshots, rollbackFeature, saveSnapshot, } from "../utils/rollback.js";
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ export function muiFeature(config) {
11
+ return {
12
+ name: "mui",
13
+ async run(projectPath) {
14
+ try {
15
+ /**
16
+ * STEP 1
17
+ * Detect TypeScript or JavaScript
18
+ */
19
+ const tsConfigPath = path.join(projectPath, "vite.config.ts");
20
+ let isTypeScript = false;
21
+ try {
22
+ await fs.access(tsConfigPath);
23
+ isTypeScript = true;
24
+ }
25
+ catch { }
26
+ /**
27
+ * STEP 2
28
+ * Install MUI core + emotion peer deps
29
+ */
30
+ spinner.start("Installing MUI...");
31
+ await execa("npm", ["install", "@mui/material", "@emotion/react", "@emotion/styled"], { cwd: projectPath });
32
+ spinner.succeed("MUI installed!");
33
+ /**
34
+ * STEP 3
35
+ * Install MUI Icons if selected or needed for ThemeToggle
36
+ */
37
+ if (config.icons || config.darkModeToggle) {
38
+ spinner.start("Installing MUI Icons...");
39
+ await execa("npm", ["install", "@mui/icons-material"], {
40
+ cwd: projectPath,
41
+ });
42
+ spinner.succeed("MUI Icons installed!");
43
+ }
44
+ /**
45
+ * STEP 4
46
+ * Generate src/themes/theme.ts or theme.js
47
+ */
48
+ spinner.start("Generating MUI theme...");
49
+ const themesDir = path.join(projectPath, "src/themes");
50
+ await fs.mkdir(themesDir, { recursive: true });
51
+ const themeFile = path.join(themesDir, isTypeScript ? "theme.ts" : "theme.js");
52
+ await fs.writeFile(themeFile, generateMuiTheme(config, isTypeScript));
53
+ spinner.succeed("MUI theme generated!");
54
+ /**
55
+ * STEP 5
56
+ * Apply font to src/index.css if a font was selected
57
+ */
58
+ if (config.font !== "none") {
59
+ spinner.start("Applying font...");
60
+ const indexCssPath = path.join(projectPath, "src/index.css");
61
+ await saveSnapshot(indexCssPath);
62
+ await fs.writeFile(indexCssPath, generateMuiIndexCss(config));
63
+ spinner.succeed("Font applied!");
64
+ }
65
+ /**
66
+ * STEP 6
67
+ * Generate src/context/ThemeContextProvider
68
+ */
69
+ spinner.start("Generating ThemeContextProvider...");
70
+ const contextDir = path.join(projectPath, "src/context");
71
+ await fs.mkdir(contextDir, { recursive: true });
72
+ const contextFile = path.join(contextDir, isTypeScript
73
+ ? "ThemeContextProvider.tsx"
74
+ : "ThemeContextProvider.jsx");
75
+ await fs.writeFile(contextFile, generateThemeContextProvider(config, isTypeScript));
76
+ spinner.succeed("ThemeContextProvider generated!");
77
+ /**
78
+ * STEP 7
79
+ * Generate src/themes/AppMuiThemeProvider
80
+ */
81
+ spinner.start("Generating AppMuiThemeProvider...");
82
+ const providerFile = path.join(themesDir, isTypeScript ? "AppMuiThemeProvider.tsx" : "AppMuiThemeProvider.jsx");
83
+ await fs.writeFile(providerFile, generateAppMuiThemeProvider(isTypeScript));
84
+ spinner.succeed("AppMuiThemeProvider generated!");
85
+ /**
86
+ * STEP 8
87
+ * Generate ThemeToggle component if dark mode toggle selected
88
+ */
89
+ if (config.darkModeToggle) {
90
+ spinner.start("Creating ThemeToggle component...");
91
+ const componentDir = path.join(projectPath, "src/components");
92
+ await fs.mkdir(componentDir, { recursive: true });
93
+ const toggleFile = path.join(componentDir, isTypeScript ? "ThemeToggle.tsx" : "ThemeToggle.jsx");
94
+ await fs.writeFile(toggleFile, generateMuiThemeToggle());
95
+ spinner.succeed("ThemeToggle component created!");
96
+ }
97
+ /**
98
+ * STEP 9
99
+ * Overwrite main.tsx / main.jsx with provider composition
100
+ */
101
+ spinner.start("Configuring main entry point...");
102
+ const mainFile = path.join(projectPath, isTypeScript ? "src/main.tsx" : "src/main.jsx");
103
+ await saveSnapshot(mainFile);
104
+ await fs.writeFile(mainFile, generateMuiMain(isTypeScript));
105
+ spinner.succeed("Main entry point configured!");
106
+ /**
107
+ * STEP 10
108
+ * Copy demo App template if user selected it
109
+ */
110
+ if (config.demo) {
111
+ spinner.start("Copying MUI demo template...");
112
+ const templateDir = path.join(__dirname, "../templates/react/mui");
113
+ const appTemplatePath = path.join(templateDir, isTypeScript ? "App.tsx" : "App.jsx");
114
+ const appTargetPath = path.join(projectPath, isTypeScript ? "src/App.tsx" : "src/App.jsx");
115
+ await fs.copyFile(appTemplatePath, appTargetPath);
116
+ spinner.succeed("Demo template copied!");
117
+ }
118
+ /**
119
+ * FINAL SUCCESS MESSAGE
120
+ */
121
+ logger.success("MUI added successfully!");
122
+ }
123
+ catch (error) {
124
+ spinner.fail("Failed to setup MUI.");
125
+ await restoreSnapshots();
126
+ await rollbackFeature([
127
+ path.join(projectPath, "src/themes"),
128
+ path.join(projectPath, "src/context"),
129
+ path.join(projectPath, "src/components/ThemeToggle.tsx"),
130
+ path.join(projectPath, "src/components/ThemeToggle.jsx"),
131
+ path.join(projectPath, "src/App.tsx"),
132
+ path.join(projectPath, "src/App.jsx"),
133
+ ]);
134
+ throw error;
135
+ }
136
+ },
137
+ };
138
+ }
@@ -0,0 +1,73 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import { spinner } from "../utils/spinner.js";
4
+ import { logger } from "../utils/logger.js";
5
+ import { installPackages } from "../utils/install.js";
6
+ import { rollbackFeature } from "../utils/rollback.js";
7
+ export function prettierFeature(config) {
8
+ return {
9
+ name: "prettier",
10
+ async run(projectPath) {
11
+ try {
12
+ if (!config.enabled) {
13
+ logger.info("Skipping Prettier setup.");
14
+ return;
15
+ }
16
+ /**
17
+ * STEP 1
18
+ * Install Prettier
19
+ */
20
+ spinner.start("Installing Prettier...");
21
+ await installPackages(["prettier"], projectPath, true);
22
+ spinner.succeed("Prettier installed!");
23
+ /**
24
+ * STEP 2
25
+ * Build the Prettier config object from user choices
26
+ */
27
+ const prettierConfig = {
28
+ singleQuote: config.singleQuote,
29
+ semi: config.semi,
30
+ tabWidth: config.tabWidth,
31
+ };
32
+ /**
33
+ * STEP 3
34
+ * Check if .prettierrc already exists (Tailwind plugin case)
35
+ * If yes — read it and merge, preserving the plugins array
36
+ * If no — create fresh
37
+ */
38
+ const prettierrcPath = path.join(projectPath, ".prettierrc");
39
+ let existingConfig = {};
40
+ try {
41
+ const raw = await fs.readFile(prettierrcPath, "utf-8");
42
+ existingConfig = JSON.parse(raw);
43
+ }
44
+ catch {
45
+ // file doesn't exist — start fresh
46
+ }
47
+ const merged = { ...prettierConfig, ...existingConfig };
48
+ spinner.start("Writing .prettierrc...");
49
+ await fs.writeFile(prettierrcPath, JSON.stringify(merged, null, 2));
50
+ spinner.succeed(".prettierrc written!");
51
+ /**
52
+ * STEP 4
53
+ * Create .prettierignore
54
+ */
55
+ spinner.start("Creating .prettierignore...");
56
+ await fs.writeFile(path.join(projectPath, ".prettierignore"), ["node_modules", "dist", "build", ""].join("\n"));
57
+ spinner.succeed(".prettierignore created!");
58
+ /**
59
+ * FINAL SUCCESS MESSAGE
60
+ */
61
+ logger.success("Prettier setup complete!");
62
+ }
63
+ catch (error) {
64
+ spinner.fail("Failed to setup Prettier.");
65
+ await rollbackFeature([
66
+ path.join(projectPath, ".prettierrc"),
67
+ path.join(projectPath, ".prettierignore"),
68
+ ]);
69
+ throw error;
70
+ }
71
+ },
72
+ };
73
+ }
@@ -0,0 +1,116 @@
1
+ import { execa } from "execa";
2
+ import fs from "fs/promises";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ import { spinner } from "../utils/spinner.js";
6
+ import { logger } from "../utils/logger.js";
7
+ import { generateIndexCss } from "../generators/css.generator.js";
8
+ import { generateThemeToggle } from "../generators/toggle.generator.js";
9
+ import { generateTailwindConfig } from "../generators/tailwind.config.generator.js";
10
+ import { restoreSnapshots, rollbackFeature, saveSnapshot, } from "../utils/rollback.js";
11
+ import { installPackages } from "../utils/install.js";
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+ export function tailwindFeature(config) {
14
+ return {
15
+ name: "tailwind",
16
+ async run(projectPath) {
17
+ try {
18
+ /**
19
+ * STEP 1
20
+ * Install Tailwind v4 and the Vite plugin
21
+ */
22
+ spinner.start("Installing Tailwind v4...");
23
+ await execa("npm", ["install", "tailwindcss", "@tailwindcss/vite", "--legacy-peer-deps"], { cwd: projectPath });
24
+ spinner.succeed("Tailwind installed!");
25
+ /**
26
+ * STEP 2
27
+ * Detect TypeScript or JavaScript
28
+ */
29
+ const tsConfigPath = path.join(projectPath, "vite.config.ts");
30
+ const jsConfigPath = path.join(projectPath, "vite.config.js");
31
+ let viteConfigPath;
32
+ let isTypeScript = false;
33
+ try {
34
+ await fs.access(tsConfigPath);
35
+ viteConfigPath = tsConfigPath;
36
+ isTypeScript = true;
37
+ }
38
+ catch {
39
+ try {
40
+ await fs.access(jsConfigPath);
41
+ viteConfigPath = jsConfigPath;
42
+ }
43
+ catch {
44
+ throw new Error("Could not find vite.config.ts or vite.config.js");
45
+ }
46
+ }
47
+ /**
48
+ * STEP 3
49
+ * Copy the Vite config template that includes the Tailwind plugin
50
+ */
51
+ spinner.start("Configuring Vite...");
52
+ const templateDir = path.join(__dirname, "../templates/react/tailwind-v4");
53
+ const viteTemplatePath = path.join(templateDir, isTypeScript ? "vite.config.ts" : "vite.config.js");
54
+ await fs.copyFile(viteTemplatePath, viteConfigPath);
55
+ spinner.succeed("Vite configured!");
56
+ /**
57
+ * STEP 4
58
+ * Generate tailwind.config based on user choices
59
+ */
60
+ spinner.start("Generating Tailwind config...");
61
+ const tailwindConfigPath = path.join(projectPath, isTypeScript ? "tailwind.config.ts" : "tailwind.config.js");
62
+ await fs.writeFile(tailwindConfigPath, generateTailwindConfig(config));
63
+ spinner.succeed("Tailwind config generated!");
64
+ /**
65
+ * STEP 5
66
+ * Generate src/index.css with font, colors, and dark mode setup
67
+ */
68
+ spinner.start("Generating Tailwind styles...");
69
+ const cssPath = path.join(projectPath, "src/index.css");
70
+ await saveSnapshot(cssPath);
71
+ await fs.writeFile(cssPath, generateIndexCss(config));
72
+ spinner.succeed("Tailwind styles generated!");
73
+ /**
74
+ * STEP 6
75
+ * Generate ThemeToggle component if user selected dark mode toggle
76
+ */
77
+ if (config.darkModeToggle) {
78
+ spinner.start("Creating ThemeToggle component...");
79
+ const componentDir = path.join(projectPath, "src/components");
80
+ await fs.mkdir(componentDir, { recursive: true });
81
+ const toggleFile = path.join(componentDir, isTypeScript ? "ThemeToggle.tsx" : "ThemeToggle.jsx");
82
+ await fs.writeFile(toggleFile, generateThemeToggle(isTypeScript));
83
+ spinner.succeed("ThemeToggle component created!");
84
+ }
85
+ /**
86
+ * STEP 7
87
+ * Install Prettier + Tailwind class sorting plugin if selected
88
+ */
89
+ if (config.prettierTailwind) {
90
+ spinner.start("Installing Prettier + Tailwind plugin...");
91
+ await installPackages(["prettier", "prettier-plugin-tailwindcss"], projectPath, true);
92
+ spinner.succeed("Prettier installed!");
93
+ spinner.start("Creating .prettierrc...");
94
+ await fs.writeFile(path.join(projectPath, ".prettierrc"), JSON.stringify({ plugins: ["prettier-plugin-tailwindcss"] }, null, 2));
95
+ spinner.succeed(".prettierrc created!");
96
+ }
97
+ /**
98
+ * FINAL SUCCESS MESSAGE
99
+ */
100
+ logger.success("Tailwind v4 added successfully!");
101
+ }
102
+ catch (error) {
103
+ spinner.fail("Failed to setup Tailwind.");
104
+ await restoreSnapshots();
105
+ await rollbackFeature([
106
+ path.join(projectPath, "tailwind.config.ts"),
107
+ path.join(projectPath, "tailwind.config.js"),
108
+ path.join(projectPath, "src/components/ThemeToggle.tsx"),
109
+ path.join(projectPath, "src/components/ThemeToggle.jsx"),
110
+ path.join(projectPath, ".prettierrc"),
111
+ ]);
112
+ throw error;
113
+ }
114
+ },
115
+ };
116
+ }
@@ -0,0 +1,75 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ // ─── Helpers ───
4
+ async function createFolder(folderPath) {
5
+ await fs.mkdir(folderPath, { recursive: true });
6
+ }
7
+ async function createGitkeep(folderPath) {
8
+ await fs.writeFile(path.join(folderPath, ".gitkeep"), "");
9
+ }
10
+ async function createIndexFile(folderPath, isTypeScript) {
11
+ const filename = isTypeScript ? "index.ts" : "index.js";
12
+ await fs.writeFile(path.join(folderPath, filename), "export {};\n");
13
+ }
14
+ async function folderExists(folderPath) {
15
+ try {
16
+ await fs.access(folderPath);
17
+ return true;
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ // ─── Main generator ───
24
+ export async function generateArchitecture(config, projectPath, isTypeScript) {
25
+ if (config.style === "skip")
26
+ return;
27
+ const srcPath = path.join(projectPath, "src");
28
+ if (config.style === "feature-based") {
29
+ // Create src/features/ folder
30
+ const featuresDir = path.join(srcPath, "features");
31
+ await createFolder(featuresDir);
32
+ // Create each feature with its selected folders
33
+ for (const feature of config.features) {
34
+ const featureDir = path.join(featuresDir, feature);
35
+ await createFolder(featureDir);
36
+ for (const folder of config.featureFolders) {
37
+ const folderPath = path.join(featureDir, folder);
38
+ await createFolder(folderPath);
39
+ if (config.indexFiles.includes(folder)) {
40
+ await createIndexFile(folderPath, isTypeScript);
41
+ }
42
+ else {
43
+ await createGitkeep(folderPath);
44
+ }
45
+ }
46
+ }
47
+ // Create shared folders in src/ — skip if already exists
48
+ for (const folder of config.sharedFolders) {
49
+ const folderPath = path.join(srcPath, folder);
50
+ if (await folderExists(folderPath))
51
+ continue;
52
+ await createFolder(folderPath);
53
+ if (config.indexFiles.includes(folder)) {
54
+ await createIndexFile(folderPath, isTypeScript);
55
+ }
56
+ else {
57
+ await createGitkeep(folderPath);
58
+ }
59
+ }
60
+ }
61
+ if (config.style === "layered") {
62
+ for (const folder of config.sharedFolders) {
63
+ const folderPath = path.join(srcPath, folder);
64
+ if (await folderExists(folderPath))
65
+ continue;
66
+ await createFolder(folderPath);
67
+ if (config.indexFiles.includes(folder)) {
68
+ await createIndexFile(folderPath, isTypeScript);
69
+ }
70
+ else {
71
+ await createGitkeep(folderPath);
72
+ }
73
+ }
74
+ }
75
+ }