create-lumen 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 (156) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +115 -0
  3. package/bin/cli.js +17 -0
  4. package/package.json +42 -0
  5. package/src/cleanup.js +7 -0
  6. package/src/config-cache.js +27 -0
  7. package/src/configure.js +161 -0
  8. package/src/confirm-empty-folder.js +45 -0
  9. package/src/css.js +59 -0
  10. package/src/dependencies.js +101 -0
  11. package/src/injector.js +276 -0
  12. package/src/main.js +197 -0
  13. package/src/prompts.js +232 -0
  14. package/src/readme.js +178 -0
  15. package/src/scaffold.js +29 -0
  16. package/src/utils/dev-server.js +34 -0
  17. package/src/utils/fs.js +74 -0
  18. package/src/utils/pkg-manager.js +17 -0
  19. package/src/utils/yarn-v1.js +10 -0
  20. package/templates/architectures/component-based/main.jsx +10 -0
  21. package/templates/architectures/component-based/main.tsx +10 -0
  22. package/templates/architectures/component-based/src/App.jsx +11 -0
  23. package/templates/architectures/component-based/src/App.tsx +11 -0
  24. package/templates/architectures/component-based/src/components/common/Button.jsx +30 -0
  25. package/templates/architectures/component-based/src/components/common/Button.tsx +35 -0
  26. package/templates/architectures/component-based/src/components/common/index.js +1 -0
  27. package/templates/architectures/component-based/src/components/common/index.ts +1 -0
  28. package/templates/architectures/component-based/src/components/form/.gitkeep +0 -0
  29. package/templates/architectures/component-based/src/components/layout/.gitkeep +0 -0
  30. package/templates/architectures/component-based/src/config/constants.js +2 -0
  31. package/templates/architectures/component-based/src/config/constants.ts +2 -0
  32. package/templates/architectures/component-based/src/config/routes.js +7 -0
  33. package/templates/architectures/component-based/src/config/routes.ts +7 -0
  34. package/templates/architectures/component-based/src/context/AppContext.jsx +13 -0
  35. package/templates/architectures/component-based/src/context/AppContext.tsx +13 -0
  36. package/templates/architectures/component-based/src/hooks/index.js +1 -0
  37. package/templates/architectures/component-based/src/hooks/index.ts +1 -0
  38. package/templates/architectures/component-based/src/hooks/useLocalStorage.jsx +28 -0
  39. package/templates/architectures/component-based/src/hooks/useLocalStorage.ts +28 -0
  40. package/templates/architectures/component-based/src/layouts/MainLayout.jsx +29 -0
  41. package/templates/architectures/component-based/src/layouts/MainLayout.tsx +33 -0
  42. package/templates/architectures/component-based/src/layouts/index.js +1 -0
  43. package/templates/architectures/component-based/src/layouts/index.ts +1 -0
  44. package/templates/architectures/component-based/src/pages/Home/Home.jsx +16 -0
  45. package/templates/architectures/component-based/src/pages/Home/Home.tsx +16 -0
  46. package/templates/architectures/component-based/src/pages/Home/index.js +1 -0
  47. package/templates/architectures/component-based/src/pages/Home/index.ts +1 -0
  48. package/templates/architectures/component-based/src/routes/index.jsx +11 -0
  49. package/templates/architectures/component-based/src/routes/index.tsx +11 -0
  50. package/templates/architectures/component-based/src/services/.gitkeep +0 -0
  51. package/templates/architectures/component-based/src/store/.gitkeep +0 -0
  52. package/templates/architectures/component-based/src/styles/globals.css +7 -0
  53. package/templates/architectures/component-based/src/utils/cn.jsx +3 -0
  54. package/templates/architectures/component-based/src/utils/cn.ts +3 -0
  55. package/templates/architectures/component-based/src/utils/index.js +1 -0
  56. package/templates/architectures/component-based/src/utils/index.ts +1 -0
  57. package/templates/architectures/feature-based/main.jsx +10 -0
  58. package/templates/architectures/feature-based/main.tsx +10 -0
  59. package/templates/architectures/feature-based/scripts/create-feature.mjs +65 -0
  60. package/templates/architectures/feature-based/src/app/App.jsx +10 -0
  61. package/templates/architectures/feature-based/src/app/App.tsx +10 -0
  62. package/templates/architectures/feature-based/src/app/providers/ThemeProvider.jsx +5 -0
  63. package/templates/architectures/feature-based/src/app/providers/ThemeProvider.tsx +5 -0
  64. package/templates/architectures/feature-based/src/components/ui/Button.jsx +23 -0
  65. package/templates/architectures/feature-based/src/components/ui/Button.tsx +28 -0
  66. package/templates/architectures/feature-based/src/components/ui/index.js +1 -0
  67. package/templates/architectures/feature-based/src/components/ui/index.ts +1 -0
  68. package/templates/architectures/feature-based/src/config/constants.js +2 -0
  69. package/templates/architectures/feature-based/src/config/constants.ts +2 -0
  70. package/templates/architectures/feature-based/src/features/home/components/.gitkeep +0 -0
  71. package/templates/architectures/feature-based/src/features/home/hooks/.gitkeep +0 -0
  72. package/templates/architectures/feature-based/src/features/home/index.js +1 -0
  73. package/templates/architectures/feature-based/src/features/home/index.ts +1 -0
  74. package/templates/architectures/feature-based/src/features/home/interfaces/.gitkeep +0 -0
  75. package/templates/architectures/feature-based/src/features/home/layouts/.gitkeep +0 -0
  76. package/templates/architectures/feature-based/src/features/home/pages/HomePage.jsx +14 -0
  77. package/templates/architectures/feature-based/src/features/home/pages/HomePage.tsx +14 -0
  78. package/templates/architectures/feature-based/src/features/home/services/.gitkeep +0 -0
  79. package/templates/architectures/feature-based/src/features/home/store/.gitkeep +0 -0
  80. package/templates/architectures/feature-based/src/hooks/index.js +1 -0
  81. package/templates/architectures/feature-based/src/hooks/index.ts +1 -0
  82. package/templates/architectures/feature-based/src/hooks/useLocalStorage.jsx +28 -0
  83. package/templates/architectures/feature-based/src/hooks/useLocalStorage.ts +28 -0
  84. package/templates/architectures/feature-based/src/lib/.gitkeep +0 -0
  85. package/templates/architectures/feature-based/src/stores/.gitkeep +0 -0
  86. package/templates/architectures/feature-based/src/styles/globals.css +1 -0
  87. package/templates/architectures/feature-based/src/types/index.js +13 -0
  88. package/templates/architectures/feature-based/src/types/index.ts +11 -0
  89. package/templates/architectures/feature-based/src/utils/cn.jsx +3 -0
  90. package/templates/architectures/feature-based/src/utils/cn.ts +6 -0
  91. package/templates/architectures/feature-based/src/utils/index.js +1 -0
  92. package/templates/architectures/feature-based/src/utils/index.ts +1 -0
  93. package/templates/conditional/axios/component-based/src/services/axios.jsx +19 -0
  94. package/templates/conditional/axios/component-based/src/services/axios.ts +19 -0
  95. package/templates/conditional/axios/component-based/src/services/index.js +1 -0
  96. package/templates/conditional/axios/component-based/src/services/index.ts +1 -0
  97. package/templates/conditional/axios/feature-based/src/lib/axios.jsx +19 -0
  98. package/templates/conditional/axios/feature-based/src/lib/axios.ts +19 -0
  99. package/templates/conditional/axios/feature-based/src/lib/index.js +1 -0
  100. package/templates/conditional/axios/feature-based/src/lib/index.ts +1 -0
  101. package/templates/conditional/icons/huge/component-based/src/pages/Home/Home.jsx +20 -0
  102. package/templates/conditional/icons/huge/component-based/src/pages/Home/Home.tsx +20 -0
  103. package/templates/conditional/icons/huge/feature-based/src/features/home/pages/HomePage.jsx +20 -0
  104. package/templates/conditional/icons/huge/feature-based/src/features/home/pages/HomePage.tsx +20 -0
  105. package/templates/conditional/icons/lucide/component-based/src/pages/Home/Home.jsx +20 -0
  106. package/templates/conditional/icons/lucide/component-based/src/pages/Home/Home.tsx +20 -0
  107. package/templates/conditional/icons/lucide/feature-based/src/features/home/pages/HomePage.jsx +20 -0
  108. package/templates/conditional/icons/lucide/feature-based/src/features/home/pages/HomePage.tsx +20 -0
  109. package/templates/conditional/linting/eslint/eslint.config.js +31 -0
  110. package/templates/conditional/linting/eslint/eslint.config.ts +28 -0
  111. package/templates/conditional/linting/oxlint/oxlintrc.json +9 -0
  112. package/templates/conditional/router/component-based/src/App.jsx +11 -0
  113. package/templates/conditional/router/component-based/src/App.tsx +11 -0
  114. package/templates/conditional/router/component-based/src/routes/index.jsx +13 -0
  115. package/templates/conditional/router/component-based/src/routes/index.tsx +13 -0
  116. package/templates/conditional/router/feature-based/src/app/App.jsx +6 -0
  117. package/templates/conditional/router/feature-based/src/app/App.tsx +6 -0
  118. package/templates/conditional/router/feature-based/src/app/router.jsx +13 -0
  119. package/templates/conditional/router/feature-based/src/app/router.tsx +13 -0
  120. package/templates/conditional/state/redux/component-based/src/context/StoreProvider.jsx +7 -0
  121. package/templates/conditional/state/redux/component-based/src/context/StoreProvider.tsx +7 -0
  122. package/templates/conditional/state/redux/component-based/src/pages/Home/counterSlice.jsx +20 -0
  123. package/templates/conditional/state/redux/component-based/src/pages/Home/counterSlice.ts +20 -0
  124. package/templates/conditional/state/redux/component-based/src/store/hooks.js +4 -0
  125. package/templates/conditional/state/redux/component-based/src/store/hooks.ts +4 -0
  126. package/templates/conditional/state/redux/component-based/src/store/index.js +8 -0
  127. package/templates/conditional/state/redux/component-based/src/store/index.ts +11 -0
  128. package/templates/conditional/state/redux/feature-based/src/app/providers/StoreProvider.jsx +7 -0
  129. package/templates/conditional/state/redux/feature-based/src/app/providers/StoreProvider.tsx +7 -0
  130. package/templates/conditional/state/redux/feature-based/src/features/home/store/counterSlice.jsx +20 -0
  131. package/templates/conditional/state/redux/feature-based/src/features/home/store/counterSlice.ts +20 -0
  132. package/templates/conditional/state/redux/feature-based/src/stores/hooks.js +4 -0
  133. package/templates/conditional/state/redux/feature-based/src/stores/hooks.ts +4 -0
  134. package/templates/conditional/state/redux/feature-based/src/stores/index.js +8 -0
  135. package/templates/conditional/state/redux/feature-based/src/stores/index.ts +11 -0
  136. package/templates/conditional/state/zustand/component-based/src/store/useCounterStore.jsx +9 -0
  137. package/templates/conditional/state/zustand/component-based/src/store/useCounterStore.ts +9 -0
  138. package/templates/conditional/state/zustand/feature-based/src/stores/useCounterStore.jsx +9 -0
  139. package/templates/conditional/state/zustand/feature-based/src/stores/useCounterStore.ts +9 -0
  140. package/templates/conditional/testing/jest/jest.config.js +12 -0
  141. package/templates/conditional/testing/jest/jest.config.ts +13 -0
  142. package/templates/conditional/testing/jest/jest.setup.js +1 -0
  143. package/templates/conditional/testing/jest/jest.setup.ts +1 -0
  144. package/templates/conditional/testing/jest/src/__tests__/App.test.jsx +9 -0
  145. package/templates/conditional/testing/jest/src/__tests__/App.test.tsx +9 -0
  146. package/templates/conditional/testing/vitest/src/__tests__/App.test.jsx +10 -0
  147. package/templates/conditional/testing/vitest/src/__tests__/App.test.tsx +10 -0
  148. package/templates/conditional/testing/vitest/src/test/setup.js +1 -0
  149. package/templates/conditional/testing/vitest/src/test/setup.ts +1 -0
  150. package/templates/conditional/testing/vitest/vitest.config.js +18 -0
  151. package/templates/conditional/testing/vitest/vitest.config.ts +18 -0
  152. package/templates/css/bootstrap/src/index.css +1 -0
  153. package/templates/css/none/src/index.css +18 -0
  154. package/templates/css/tailwind/src/index.css +1 -0
  155. package/templates/css/tailwind/vite.config.js +15 -0
  156. package/templates/css/tailwind/vite.config.ts +15 -0
@@ -0,0 +1,276 @@
1
+ import { promises as fsp } from "fs";
2
+ import path from "path";
3
+ import { copyDirRecursive, writeFileRecursive } from "./utils/fs.js";
4
+
5
+ export async function injectArchitecture(projectPath, templatesDir, architecture, language) {
6
+ const ext = language === "ts" ? "tsx" : "jsx";
7
+ const srcDir = path.join(templatesDir, "architectures", architecture, "src");
8
+
9
+ // Remove Vite's default App files BEFORE copying architecture
10
+ const defaultAppTsx = path.join(projectPath, "src", "App.tsx");
11
+ const defaultAppJsx = path.join(projectPath, "src", "App.jsx");
12
+ try { await fsp.rm(defaultAppTsx, { force: true }); } catch {}
13
+ try { await fsp.rm(defaultAppJsx, { force: true }); } catch {}
14
+
15
+ // Copy the architecture tree into the project's src/
16
+ await copyDirRecursive(srcDir, path.join(projectPath, "src"), (name) => {
17
+ // Skip files that don't match the language
18
+ if (language === "ts" && name.endsWith(".jsx")) return false;
19
+ if (language === "js" && name.endsWith(".tsx")) return false;
20
+ // Skip .ts files when JS, skip .js when TS (for non-component files)
21
+ if (language === "ts" && name.endsWith(".js") && !name.endsWith(".config.js")) return false;
22
+ if (language === "js" && name.endsWith(".ts") && !name.endsWith(".config.ts")) return false;
23
+ return true;
24
+ });
25
+
26
+ // Copy the architecture's main.tsx/main.jsx to src/main.{ext}
27
+ const mainSrc = path.join(
28
+ templatesDir,
29
+ "architectures",
30
+ architecture,
31
+ `main.${ext}`
32
+ );
33
+ try {
34
+ await fsp.access(mainSrc);
35
+ await fsp.copyFile(mainSrc, path.join(projectPath, "src", `main.${ext}`));
36
+ } catch {
37
+ // main file doesn't exist in template, skip
38
+ }
39
+
40
+ // Remove the opposite language's main file (Vite creates main.jsx by default)
41
+ const oppositeExt = language === "ts" ? "jsx" : "tsx";
42
+ const oppositeMain = path.join(projectPath, "src", `main.${oppositeExt}`);
43
+ try { await fsp.rm(oppositeMain, { force: true }); } catch {}
44
+ }
45
+
46
+ export async function injectConditionals(projectPath, templatesDir, responses, architecture, language) {
47
+ const ext = language === "ts" ? "tsx" : "jsx";
48
+
49
+ // 1. State management
50
+ if (responses.stateManagement === "redux") {
51
+ await injectOverlay(
52
+ projectPath,
53
+ templatesDir,
54
+ `conditional/state/redux/${architecture}`,
55
+ language
56
+ );
57
+ await updateMainWithProvider(projectPath, "redux", architecture, ext);
58
+ } else if (responses.stateManagement === "zustand") {
59
+ await injectOverlay(
60
+ projectPath,
61
+ templatesDir,
62
+ `conditional/state/zustand/${architecture}`,
63
+ language
64
+ );
65
+ }
66
+
67
+ // 2. Router (overwrites App.tsx with routed version)
68
+ if (responses.router) {
69
+ await injectOverlay(
70
+ projectPath,
71
+ templatesDir,
72
+ `conditional/router/${architecture}`,
73
+ language
74
+ );
75
+ }
76
+
77
+ // 3. Icons (overwrites home page)
78
+ if (responses.iconLibrary && responses.iconLibrary !== "none") {
79
+ await injectOverlay(
80
+ projectPath,
81
+ templatesDir,
82
+ `conditional/icons/${responses.iconLibrary}/${architecture}`,
83
+ language
84
+ );
85
+ }
86
+
87
+ // 4. Axios
88
+ if (responses.axios) {
89
+ await injectOverlay(
90
+ projectPath,
91
+ templatesDir,
92
+ `conditional/axios/${architecture}`,
93
+ language
94
+ );
95
+ }
96
+
97
+ // 5. Testing
98
+ if (responses.testing === "vitest") {
99
+ await injectTesting(projectPath, templatesDir, "vitest", language, architecture);
100
+ } else if (responses.testing === "jest") {
101
+ await injectTesting(projectPath, templatesDir, "jest", language, architecture);
102
+ }
103
+
104
+ // 6. Linter
105
+ if (responses.linter === "eslint") {
106
+ await injectLinter(projectPath, templatesDir, "eslint", language);
107
+ } else if (responses.linter === "oxlint") {
108
+ await injectLinter(projectPath, templatesDir, "oxlint", language);
109
+ }
110
+
111
+ // 7. Create feature script (feature-based only)
112
+ if (architecture === "feature-based") {
113
+ await injectCreateFeatureScript(projectPath, templatesDir, language);
114
+ }
115
+ }
116
+
117
+ async function injectOverlay(projectPath, templatesDir, overlayPath, language) {
118
+ const srcDir = path.join(templatesDir, overlayPath, "src");
119
+ try {
120
+ await fsp.access(srcDir);
121
+ await copyDirRecursive(srcDir, path.join(projectPath, "src"), (name) => {
122
+ if (language === "ts" && name.endsWith(".jsx")) return false;
123
+ if (language === "js" && name.endsWith(".tsx")) return false;
124
+ if (language === "ts" && name.endsWith(".js") && !name.endsWith(".config.js")) return false;
125
+ if (language === "js" && name.endsWith(".ts") && !name.endsWith(".config.ts")) return false;
126
+ return true;
127
+ });
128
+ } catch {
129
+ // overlay src dir doesn't exist
130
+ }
131
+ }
132
+
133
+ async function injectTesting(projectPath, templatesDir, framework, language, architecture) {
134
+ const testingDir = path.join(templatesDir, "conditional", "testing", framework);
135
+
136
+ // Copy config files to project root
137
+ const configExt = language === "ts" ? "ts" : "js";
138
+ const configSrc = path.join(testingDir, `${framework}.config.${configExt}`);
139
+ try {
140
+ await fsp.access(configSrc);
141
+ await fsp.copyFile(configSrc, path.join(projectPath, `${framework}.config.${configExt}`));
142
+ } catch {}
143
+
144
+ // Copy setup file
145
+ const setupSrc = path.join(testingDir, `src/test/setup.${configExt}`);
146
+ try {
147
+ await fsp.access(setupSrc);
148
+ const setupDest = path.join(projectPath, "src", "test", `setup.${configExt}`);
149
+ await writeFileRecursive(setupDest, await fsp.readFile(setupSrc, "utf8"));
150
+ } catch {}
151
+
152
+ // Copy test file
153
+ const testExt = language === "ts" ? "tsx" : "jsx";
154
+ const testSrc = path.join(testingDir, `src/__tests__/App.test.${testExt}`);
155
+ try {
156
+ await fsp.access(testSrc);
157
+ const testDest = path.join(projectPath, "src", "__tests__", `App.test.${testExt}`);
158
+ await writeFileRecursive(testDest, await fsp.readFile(testSrc, "utf8"));
159
+ } catch {}
160
+
161
+ // Add test script to package.json
162
+ const pkgPath = path.join(projectPath, "package.json");
163
+ try {
164
+ const content = await fsp.readFile(pkgPath, "utf8");
165
+ const pkg = JSON.parse(content);
166
+ if (!pkg.scripts) pkg.scripts = {};
167
+ if (framework === "vitest") {
168
+ pkg.scripts.test = "vitest";
169
+ pkg.scripts["test:run"] = "vitest run";
170
+ } else {
171
+ pkg.scripts.test = "jest";
172
+ pkg.scripts["test:watch"] = "jest --watch";
173
+ }
174
+ await fsp.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
175
+ } catch {}
176
+ }
177
+
178
+ async function injectLinter(projectPath, templatesDir, linter, language) {
179
+ const lintingDir = path.join(templatesDir, "conditional", "linting", linter);
180
+
181
+ if (linter === "eslint") {
182
+ const configExt = language === "ts" ? "ts" : "js";
183
+ const configSrc = path.join(lintingDir, `eslint.config.${configExt}`);
184
+ try {
185
+ await fsp.access(configSrc);
186
+ await fsp.copyFile(configSrc, path.join(projectPath, `eslint.config.${configExt}`));
187
+ } catch {}
188
+ } else if (linter === "oxlint") {
189
+ const configSrc = path.join(lintingDir, "oxlintrc.json");
190
+ try {
191
+ await fsp.access(configSrc);
192
+ await fsp.copyFile(configSrc, path.join(projectPath, "oxlintrc.json"));
193
+ } catch {}
194
+ }
195
+
196
+ // Add lint script to package.json
197
+ const pkgPath = path.join(projectPath, "package.json");
198
+ try {
199
+ const content = await fsp.readFile(pkgPath, "utf8");
200
+ const pkg = JSON.parse(content);
201
+ if (!pkg.scripts) pkg.scripts = {};
202
+ if (linter === "eslint") {
203
+ pkg.scripts.lint = "eslint .";
204
+ pkg.scripts["lint:fix"] = "eslint . --fix";
205
+ } else {
206
+ pkg.scripts.lint = "oxlint .";
207
+ }
208
+ await fsp.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
209
+ } catch {}
210
+ }
211
+
212
+ async function injectCreateFeatureScript(projectPath, templatesDir, language) {
213
+ const scriptSrc = path.join(templatesDir, "architectures", "feature-based", "scripts", "create-feature.mjs");
214
+ const scriptDest = path.join(projectPath, "scripts", "create-feature.mjs");
215
+
216
+ try {
217
+ await fsp.access(scriptSrc);
218
+ let content = await fsp.readFile(scriptSrc, "utf8");
219
+
220
+ // Adapt to JS if needed
221
+ if (language === "js") {
222
+ // Replace .ts extensions with .js in the initialFiles
223
+ content = content
224
+ .replace(/interfaces\/index\.ts/g, "interfaces/index.js")
225
+ .replace(/services\/index\.ts/g, "services/index.js")
226
+ .replace(/store\/index\.ts/g, "store/index.js")
227
+ .replace(/hooks\/index\.ts/g, "hooks/index.js")
228
+ .replace(/pages\/index\.ts/g, "pages/index.js");
229
+ }
230
+
231
+ await writeFileRecursive(scriptDest, content);
232
+ } catch {}
233
+
234
+ // Add script to package.json
235
+ const pkgPath = path.join(projectPath, "package.json");
236
+ try {
237
+ const content = await fsp.readFile(pkgPath, "utf8");
238
+ const pkg = JSON.parse(content);
239
+ if (!pkg.scripts) pkg.scripts = {};
240
+ pkg.scripts["create:feature"] = "node scripts/create-feature.mjs";
241
+ await fsp.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
242
+ } catch {}
243
+ }
244
+
245
+ async function updateMainWithProvider(projectPath, stateType, architecture, ext) {
246
+ const mainPath = path.join(projectPath, "src", `main.${ext}`);
247
+ try {
248
+ let content = await fsp.readFile(mainPath, "utf8");
249
+
250
+ if (architecture === "feature-based") {
251
+ if (!content.includes("StoreProvider")) {
252
+ content = content.replace(
253
+ /import App from ['"]@\/app\/App['"]\n?/,
254
+ "import App from '@/app/App'\nimport { StoreProvider } from '@/app/providers/StoreProvider'\n"
255
+ );
256
+ content = content.replace(
257
+ /<App \/>/,
258
+ "<StoreProvider>\n <App />\n </StoreProvider>"
259
+ );
260
+ }
261
+ } else {
262
+ if (!content.includes("StoreProvider")) {
263
+ content = content.replace(
264
+ /import App from ['"]\.\/App['"]\n?/,
265
+ "import App from './App'\nimport { StoreProvider } from './context/StoreProvider'\n"
266
+ );
267
+ content = content.replace(
268
+ /<App \/>/,
269
+ "<StoreProvider>\n <App />\n </StoreProvider>"
270
+ );
271
+ }
272
+ }
273
+
274
+ await fsp.writeFile(mainPath, content, "utf8");
275
+ } catch {}
276
+ }
package/src/main.js ADDED
@@ -0,0 +1,197 @@
1
+ import { isCancel, log, spinner, text } from "@clack/prompts";
2
+ import chalk from "chalk";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+
6
+ import { confirmEmptyFolder } from "./confirm-empty-folder.js";
7
+ import { cleanupBoilerplate } from "./cleanup.js";
8
+ import { configureProject } from "./configure.js";
9
+ import { setupCssFramework } from "./css.js";
10
+ import { injectArchitecture, injectConditionals } from "./injector.js";
11
+ import { getPkgManager } from "./utils/pkg-manager.js";
12
+ import { handleDevServer } from "./utils/dev-server.js";
13
+ import { getUserInputs, onCancel } from "./prompts.js";
14
+ import { runBaseInstall, runGitInit, runViteCreate } from "./scaffold.js";
15
+ import { installAllDeps } from "./dependencies.js";
16
+ import { generateReadme } from "./readme.js";
17
+
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = path.dirname(__filename);
20
+ const TEMPLATES_DIR = path.join(__dirname, "../templates");
21
+ const CURRENT_DIR = process.cwd();
22
+
23
+ export async function main() {
24
+ const pkg = getPkgManager();
25
+
26
+ // 1. Project name
27
+ const cliArg = process.argv[2];
28
+ let projectName;
29
+
30
+ if (cliArg && cliArg.trim()) {
31
+ projectName = cliArg.trim();
32
+ log.step(chalk.gray(`Project name: ${chalk.bold(projectName)}`));
33
+ } else {
34
+ projectName = await text({
35
+ message: "What is your project name?",
36
+ placeholder: "my-app",
37
+ validate(value) {
38
+ if (!value || !value.trim()) return "Project name is required!";
39
+ },
40
+ });
41
+ if (isCancel(projectName)) onCancel();
42
+ }
43
+
44
+ // 2. Handle existing folder
45
+ await confirmEmptyFolder(projectName);
46
+
47
+ // 3. Collect user preferences
48
+ const responses = await getUserInputs(projectName);
49
+
50
+ const projectPath = path.resolve(CURRENT_DIR, projectName);
51
+
52
+ // 4. Scaffold via vite create
53
+ const createSpin = spinner();
54
+ createSpin.start("Creating your Vite + React project...");
55
+ try {
56
+ await runViteCreate(projectPath, projectName, pkg, responses.language);
57
+ createSpin.stop(chalk.green("Project created successfully!"));
58
+ } catch (err) {
59
+ createSpin.stop(chalk.red("Failed to create project."));
60
+ throw err;
61
+ }
62
+
63
+ // 5. Install base dependencies
64
+ const depSpin = spinner();
65
+ depSpin.start(`Installing dependencies using ${pkg}...`);
66
+ try {
67
+ await runBaseInstall(projectPath, pkg);
68
+ depSpin.stop(chalk.green("Dependencies installed."));
69
+ } catch (err) {
70
+ depSpin.stop(chalk.red("Failed to install dependencies."));
71
+ throw err;
72
+ }
73
+
74
+ // 6. Git init (if requested)
75
+ if (responses.gitInit) {
76
+ const gitSpin = spinner();
77
+ gitSpin.start("Initializing git repository...");
78
+ try {
79
+ await runGitInit(projectPath);
80
+ gitSpin.stop(chalk.green("Git repository initialized."));
81
+ } catch (err) {
82
+ gitSpin.stop(chalk.red("Git initialization failed."));
83
+ throw err;
84
+ }
85
+ }
86
+
87
+ // 7. Inject architecture templates
88
+ const archSpin = spinner();
89
+ archSpin.start(
90
+ `Setting up ${responses.architecture === "feature-based" ? "feature-based" : "component-based"} architecture...`
91
+ );
92
+ try {
93
+ await injectArchitecture(
94
+ projectPath,
95
+ TEMPLATES_DIR,
96
+ responses.architecture,
97
+ responses.language
98
+ );
99
+ archSpin.stop(chalk.green("Architecture scaffolding complete."));
100
+ } catch (err) {
101
+ archSpin.stop(chalk.red("Failed to scaffold architecture."));
102
+ throw err;
103
+ }
104
+
105
+ // 8. Inject conditional features
106
+ const condSpin = spinner();
107
+ condSpin.start("Installing additional features...");
108
+ try {
109
+ await injectConditionals(
110
+ projectPath,
111
+ TEMPLATES_DIR,
112
+ responses,
113
+ responses.architecture,
114
+ responses.language
115
+ );
116
+ condSpin.stop(chalk.green("Features configured."));
117
+ } catch (err) {
118
+ condSpin.stop(chalk.red("Failed to configure features."));
119
+ throw err;
120
+ }
121
+
122
+ // 9. Install all additional dependencies in one batch
123
+ const extrasSpin = spinner();
124
+ extrasSpin.start("Installing additional dependencies...");
125
+ try {
126
+ await installAllDeps(responses, pkg, projectPath);
127
+ extrasSpin.stop(chalk.green("Additional dependencies installed."));
128
+ } catch (err) {
129
+ extrasSpin.stop(chalk.red("Failed to install additional dependencies."));
130
+ throw err;
131
+ }
132
+
133
+ // 10. CSS framework setup
134
+ const cssSpin = spinner();
135
+ const cssLabel =
136
+ responses.cssFramework === "none"
137
+ ? "CSS reset"
138
+ : responses.cssFramework === "tailwind"
139
+ ? "Tailwind CSS"
140
+ : "Bootstrap";
141
+ if (responses.cssFramework !== "none") {
142
+ cssSpin.start(`Setting up ${cssLabel}...`);
143
+ }
144
+ try {
145
+ await setupCssFramework({
146
+ projectPath,
147
+ templatesDir: TEMPLATES_DIR,
148
+ language: responses.language,
149
+ cssFramework: responses.cssFramework,
150
+ ext: responses.language === "ts" ? "tsx" : "jsx",
151
+ pkg,
152
+ });
153
+ if (responses.cssFramework !== "none") {
154
+ cssSpin.stop(chalk.green(`${cssLabel} setup complete.`));
155
+ }
156
+ } catch (err) {
157
+ cssSpin.stop(chalk.red(`Failed to setup ${cssLabel}.`));
158
+ throw err;
159
+ }
160
+
161
+ // 11. Configure path aliases + package.json
162
+ const configSpin = spinner();
163
+ configSpin.start("Configuring project...");
164
+ try {
165
+ await configureProject(projectPath, responses.language, responses.cssFramework);
166
+ configSpin.stop(chalk.green("Project configured."));
167
+ } catch (err) {
168
+ configSpin.stop(chalk.red("Failed to configure project."));
169
+ throw err;
170
+ }
171
+
172
+ // 12. Cleanup boilerplate
173
+ await cleanupBoilerplate(projectPath);
174
+
175
+ // 13. Generate README.md + LICENSE (if requested)
176
+ if (responses.readme) {
177
+ const readmeSpin = spinner();
178
+ readmeSpin.start("Generating README.md and LICENSE...");
179
+ try {
180
+ await generateReadme(projectPath, projectName, responses);
181
+ readmeSpin.stop(chalk.green("README.md and LICENSE generated."));
182
+ } catch (err) {
183
+ readmeSpin.stop(chalk.red("Failed to generate README.md and LICENSE."));
184
+ throw err;
185
+ }
186
+ }
187
+
188
+ log.step(chalk.green("\nProject setup complete!"));
189
+ log.message(
190
+ chalk.gray(
191
+ ` Architecture: ${responses.architecture === "feature-based" ? "Feature-based" : "Component-based"}`
192
+ )
193
+ );
194
+
195
+ // 14. Offer dev server
196
+ await handleDevServer(pkg, projectName);
197
+ }
package/src/prompts.js ADDED
@@ -0,0 +1,232 @@
1
+ import { cancel, confirm, isCancel, note, select, text } from "@clack/prompts";
2
+ import chalk from "chalk";
3
+ import { configExists, loadConfig, saveConfig } from "./config-cache.js";
4
+
5
+ export function onCancel() {
6
+ console.log(chalk.gray("\n\nOperation cancelled.\n"));
7
+ process.exit(1);
8
+ }
9
+
10
+ function formatConfig(responses) {
11
+ const lang = responses.language === "ts" ? "TypeScript" : "JavaScript";
12
+ const arch =
13
+ responses.architecture === "feature-based"
14
+ ? "Feature-based"
15
+ : "Component-based";
16
+ const css =
17
+ responses.cssFramework === "none"
18
+ ? "None"
19
+ : responses.cssFramework === "tailwind"
20
+ ? "Tailwind CSS"
21
+ : "Bootstrap";
22
+ const testing =
23
+ responses.testing === "none"
24
+ ? "None"
25
+ : responses.testing === "vitest"
26
+ ? "Vitest"
27
+ : "Jest";
28
+ const state =
29
+ responses.stateManagement === "none"
30
+ ? "None"
31
+ : responses.stateManagement === "redux"
32
+ ? "Redux Toolkit"
33
+ : "Zustand";
34
+
35
+ return [
36
+ `• ${chalk.bold("Architecture:")} ${chalk.green(arch)}`,
37
+ `• ${chalk.bold("Language:")} ${chalk.green(lang)}`,
38
+ `• ${chalk.bold("CSS:")} ${chalk.yellow(css)}`,
39
+ `• ${chalk.bold("Testing:")} ${chalk.magenta(testing)}`,
40
+ `• ${chalk.bold("Router:")} ${responses.router ? chalk.green("Yes") : chalk.red("No")}`,
41
+ `• ${chalk.bold("State:")} ${chalk.blue(state)}`,
42
+ `• ${chalk.bold("Icons:")} ${chalk.blue(responses.iconLibrary === "none" ? "None" : responses.iconLibrary === "lucide" ? "Lucide" : "Huge")}`,
43
+ `• ${chalk.bold("Axios:")} ${responses.axios ? chalk.green("Yes") : chalk.red("No")}`,
44
+ `• ${chalk.bold("Linter:")} ${responses.linter === "oxlint" ? chalk.green("Oxlint") : responses.linter === "eslint" ? chalk.yellow("ESLint") : chalk.red("None")}`,
45
+ `• ${chalk.bold("Git:")} ${responses.gitInit ? chalk.green("Yes") : chalk.red("No")}`,
46
+ `• ${chalk.bold("README:")} ${responses.readme ? chalk.green("Yes") : chalk.red("No")}`,
47
+ ].join("\n");
48
+ }
49
+
50
+ export async function getUserInputs(projectName) {
51
+ let oldConfig = {};
52
+ let useOldConfig = false;
53
+
54
+ if (await configExists()) {
55
+ oldConfig = await loadConfig();
56
+ oldConfig.projectName = projectName;
57
+
58
+ note(
59
+ `Previous setup found:\n\n${formatConfig(oldConfig)}\n`,
60
+ "Previous Configuration"
61
+ );
62
+
63
+ const useExisting = await select({
64
+ message: "How would you like to proceed?",
65
+ options: [
66
+ { value: true, label: "Continue with previous setup" },
67
+ { value: false, label: "Start fresh (new setup)" },
68
+ ],
69
+ initialValue: true,
70
+ });
71
+
72
+ if (isCancel(useExisting)) onCancel();
73
+ useOldConfig = useExisting;
74
+
75
+ if (useOldConfig) {
76
+ return { ...oldConfig, projectName };
77
+ }
78
+ }
79
+
80
+ // Quick Setup
81
+ const quickSetup = await confirm({
82
+ message:
83
+ "Quick Setup? (TypeScript + Tailwind + Feature-based + Router + ESLint + Vitest)",
84
+ initialValue: true,
85
+ });
86
+ if (isCancel(quickSetup)) onCancel();
87
+
88
+ if (quickSetup) {
89
+ const responses = {
90
+ projectName,
91
+ architecture: "feature-based",
92
+ language: "ts",
93
+ cssFramework: "tailwind",
94
+ testing: "vitest",
95
+ router: true,
96
+ stateManagement: "none",
97
+ iconLibrary: "none",
98
+ axios: false,
99
+ linter: "eslint",
100
+ gitInit: false,
101
+ readme: true,
102
+ };
103
+ await saveConfig(responses);
104
+ return responses;
105
+ }
106
+
107
+ // Custom setup
108
+ const architecture = await select({
109
+ message: "Which project architecture do you want?",
110
+ options: [
111
+ {
112
+ label: "Feature-based",
113
+ value: "feature-based",
114
+ hint: "Scales to large apps — code grouped by business domain",
115
+ },
116
+ {
117
+ label: "Component-based",
118
+ value: "component-based",
119
+ hint: "Small apps / component libraries — code grouped by UI type",
120
+ },
121
+ ],
122
+ initialValue: "feature-based",
123
+ });
124
+ if (isCancel(architecture)) onCancel();
125
+
126
+ const language = await select({
127
+ message: "Which language do you want to use?",
128
+ options: [
129
+ { label: "TypeScript", value: "ts" },
130
+ { label: "JavaScript", value: "js" },
131
+ ],
132
+ initialValue: "ts",
133
+ });
134
+ if (isCancel(language)) onCancel();
135
+
136
+ const cssFramework = await select({
137
+ message: "Which CSS framework do you want to use?",
138
+ options: [
139
+ { label: "Tailwind CSS", value: "tailwind" },
140
+ { label: "Bootstrap", value: "bootstrap" },
141
+ { label: "None", value: "none" },
142
+ ],
143
+ initialValue: "tailwind",
144
+ });
145
+ if (isCancel(cssFramework)) onCancel();
146
+
147
+ const testing = await select({
148
+ message: "Which testing framework do you want to set up?",
149
+ options: [
150
+ { label: "Vitest", value: "vitest" },
151
+ { label: "Jest", value: "jest" },
152
+ { label: "None", value: "none" },
153
+ ],
154
+ initialValue: "vitest",
155
+ });
156
+ if (isCancel(testing)) onCancel();
157
+
158
+ const router = await confirm({
159
+ message: "Would you like to install React Router?",
160
+ initialValue: true,
161
+ });
162
+ if (isCancel(router)) onCancel();
163
+
164
+ const stateManagement = await select({
165
+ message: "Which state management library do you want to use?",
166
+ options: [
167
+ { label: "None", value: "none" },
168
+ { label: "Redux Toolkit", value: "redux" },
169
+ { label: "Zustand", value: "zustand" },
170
+ ],
171
+ initialValue: "none",
172
+ });
173
+ if (isCancel(stateManagement)) onCancel();
174
+
175
+ const iconLibrary = await select({
176
+ message: "Which icon library would you like to use?",
177
+ options: [
178
+ { label: "None", value: "none" },
179
+ { label: "Lucide Icons", value: "lucide" },
180
+ { label: "Huge Icons", value: "huge" },
181
+ ],
182
+ initialValue: "none",
183
+ });
184
+ if (isCancel(iconLibrary)) onCancel();
185
+
186
+ const axios = await confirm({
187
+ message: "Would you like to install Axios?",
188
+ initialValue: false,
189
+ });
190
+ if (isCancel(axios)) onCancel();
191
+
192
+ const gitInit = await confirm({
193
+ message: "Would you like to initialize a Git repository?",
194
+ initialValue: false,
195
+ });
196
+ if (isCancel(gitInit)) onCancel();
197
+
198
+ const linter = await select({
199
+ message: "Which linter do you want to use?",
200
+ options: [
201
+ { label: "ESLint", value: "eslint", hint: "Industry standard JavaScript linter (Recommended)" },
202
+ { label: "Oxlint", value: "oxlint", hint: "Fast Rust-based linter" },
203
+ { label: "None", value: "none" },
204
+ ],
205
+ initialValue: "eslint",
206
+ });
207
+ if (isCancel(linter)) onCancel();
208
+
209
+ const readme = await confirm({
210
+ message: "Would you like to generate a README.md and LICENSE?",
211
+ initialValue: true,
212
+ });
213
+ if (isCancel(readme)) onCancel();
214
+
215
+ const responses = {
216
+ projectName,
217
+ architecture,
218
+ language,
219
+ cssFramework,
220
+ testing,
221
+ router,
222
+ stateManagement,
223
+ iconLibrary,
224
+ axios,
225
+ linter,
226
+ gitInit,
227
+ readme,
228
+ };
229
+
230
+ await saveConfig(responses);
231
+ return responses;
232
+ }