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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # create-lumen
2
+
3
+ Scaffold a React + Vite project with your choice of **feature-based** or **component-based** architecture.
4
+
5
+ ```bash
6
+ npm create lumen
7
+ ```
8
+
9
+ ## What it does
10
+
11
+ Creates a production-ready React + Vite project with:
12
+
13
+ - **Architecture choice** — feature-based (domain-driven) or component-based (grouped by type)
14
+ - **TypeScript or JavaScript**
15
+ - **CSS framework** — Tailwind CSS, Bootstrap, or vanilla CSS
16
+ - **State management** — Zustand, Redux Toolkit, or none
17
+ - **React Router** — client-side routing (optional)
18
+ - **Testing** — Vitest or Jest (optional)
19
+ - **Linter** — ESLint (default) or Oxlint
20
+ - **Icon library** — Lucide React, Huge Icons, or none
21
+ - **Axios** — pre-configured HTTP client (optional)
22
+ - **README + LICENSE** — auto-generated (MIT)
23
+ - **Git init** — auto-initialize a git repo
24
+ - **Path alias** — `@/` imports configured out of the box
25
+
26
+ Your choices are cached and used as defaults next time.
27
+
28
+ ## Quick start
29
+
30
+ ### From npm (recommended)
31
+
32
+ ```bash
33
+ npm create lumen my-app
34
+ cd my-app
35
+ npm run dev
36
+ ```
37
+
38
+ ### From this repo
39
+
40
+ ```bash
41
+ git clone https://github.com/Its-JrDev/create-lumen.git
42
+ cd create-lumen
43
+ npm install
44
+ npm link
45
+ npx create-lumen my-app
46
+ cd my-app
47
+ npm run dev
48
+ ```
49
+
50
+ ### Quick setup defaults
51
+
52
+ TypeScript · Tailwind CSS · Feature-based · Router · ESLint · Vitest
53
+
54
+ ## Architecture
55
+
56
+ ### Feature-based
57
+
58
+ Organized by domain. Each feature is self-contained:
59
+
60
+ ```
61
+ src/
62
+ ├── app/ # App root
63
+ ├── features/
64
+ │ └── home/
65
+ │ ├── pages/
66
+ │ ├── components/
67
+ │ └── services/
68
+ ├── layouts/
69
+ ├── pages/
70
+ ├── routes/
71
+ ├── store/
72
+ ├── hooks/
73
+ └── utils/
74
+ ```
75
+
76
+ Scaffold new features instantly:
77
+
78
+ ```bash
79
+ npm run create:feature auth
80
+ ```
81
+
82
+ ### Component-based
83
+
84
+ Organized by type. Components grouped by their role:
85
+
86
+ ```
87
+ src/
88
+ ├── components/
89
+ │ ├── common/ # Shared, reusable
90
+ │ ├── form/ # Form elements
91
+ │ └── layout/ # Layout primitives
92
+ ├── pages/
93
+ ├── layouts/
94
+ ├── routes/
95
+ ├── store/
96
+ ├── hooks/
97
+ └── utils/
98
+ ```
99
+
100
+ ## Path alias
101
+
102
+ All projects use the `@/` path alias:
103
+
104
+ ```tsx
105
+ import { Button } from "@/components/common/Button";
106
+ import { useAuth } from "@/hooks/useAuth";
107
+ ```
108
+
109
+ ## Requirements
110
+
111
+ - Node.js >= 18
112
+
113
+ ## License
114
+
115
+ MIT
package/bin/cli.js ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../src/main.js";
4
+ import chalk from "chalk";
5
+
6
+ console.log(
7
+ chalk.bold.cyan("\n ✦ LUMEN") +
8
+ chalk.gray(" v1.0.0") +
9
+ chalk.gray(
10
+ "\n Scaffold a React + Vite project with architecture choice\n"
11
+ )
12
+ );
13
+
14
+ main().catch((e) => {
15
+ console.error(chalk.red("\nError:"), e.message || e);
16
+ process.exit(1);
17
+ });
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "create-lumen",
3
+ "version": "1.0.0",
4
+ "description": "Scaffold a React + Vite project with architecture choice: feature-based or component-based.",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-lumen": "bin/cli.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "src/",
12
+ "templates/",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "test": "echo \"Error: no test specified\" && exit 1"
18
+ },
19
+ "keywords": [
20
+ "scaffold",
21
+ "create",
22
+ "cli",
23
+ "vite",
24
+ "react",
25
+ "typescript",
26
+ "feature-based",
27
+ "component-based",
28
+ "architecture",
29
+ "starter",
30
+ "boilerplate"
31
+ ],
32
+ "author": "",
33
+ "license": "MIT",
34
+ "dependencies": {
35
+ "@clack/prompts": "^0.11.0",
36
+ "chalk": "^5.6.2",
37
+ "execa": "^9.6.0"
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ }
42
+ }
package/src/cleanup.js ADDED
@@ -0,0 +1,7 @@
1
+ import { deleteIfExists } from "./utils/fs.js";
2
+
3
+ export async function cleanupBoilerplate(projectPath) {
4
+ await deleteIfExists(`${projectPath}/public`);
5
+ await deleteIfExists(`${projectPath}/src/assets`);
6
+ await deleteIfExists(`${projectPath}/.oxlintrc.json`);
7
+ }
@@ -0,0 +1,27 @@
1
+ import { access, readFile, writeFile } from "fs/promises";
2
+ import os from "os";
3
+ import path from "path";
4
+
5
+ const CONFIG_PATH = path.join(os.homedir(), ".lumen-config.json");
6
+
7
+ export async function configExists() {
8
+ try {
9
+ await access(CONFIG_PATH);
10
+ return true;
11
+ } catch {
12
+ return false;
13
+ }
14
+ }
15
+
16
+ export async function loadConfig() {
17
+ try {
18
+ const content = await readFile(CONFIG_PATH, "utf-8");
19
+ return JSON.parse(content);
20
+ } catch {
21
+ return {};
22
+ }
23
+ }
24
+
25
+ export async function saveConfig(data) {
26
+ await writeFile(CONFIG_PATH, JSON.stringify(data, null, 2), "utf-8");
27
+ }
@@ -0,0 +1,161 @@
1
+ import { promises as fsp } from "fs";
2
+ import path from "path";
3
+ import { readFileContent } from "./utils/fs.js";
4
+
5
+ export async function configureProject(projectPath, language, cssFramework) {
6
+ const ext = language === "ts" ? "ts" : "js";
7
+
8
+ // Set up tsconfig/jsconfig with @/* paths
9
+ if (language === "ts") {
10
+ await configureTsconfig(projectPath);
11
+ } else {
12
+ await configureJsconfig(projectPath);
13
+ }
14
+
15
+ // Add tsconfigPaths: true to vite.config for non-tailwind setups
16
+ if (cssFramework !== "tailwind") {
17
+ await addTsconfigPathsToViteConfig(projectPath, ext);
18
+ }
19
+
20
+ // Add scripts to package.json
21
+ await configurePackageJson(projectPath);
22
+ }
23
+
24
+ async function configureTsconfig(projectPath) {
25
+ // Create/update tsconfig.app.json with paths
26
+ const tsconfigAppPath = path.join(projectPath, "tsconfig.app.json");
27
+ try {
28
+ const content = await readFileContent(tsconfigAppPath);
29
+ const config = JSON.parse(content);
30
+
31
+ if (!config.compilerOptions) config.compilerOptions = {};
32
+ config.compilerOptions.baseUrl = ".";
33
+ config.compilerOptions.paths = {
34
+ "@/*": ["./src/*"],
35
+ };
36
+
37
+ await fsp.writeFile(
38
+ tsconfigAppPath,
39
+ JSON.stringify(config, null, 2) + "\n",
40
+ "utf8"
41
+ );
42
+ } catch {
43
+ // Create tsconfig.app.json if it doesn't exist
44
+ const config = {
45
+ compilerOptions: {
46
+ target: "ES2020",
47
+ useDefineForClassFields: true,
48
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
49
+ module: "ESNext",
50
+ skipLibCheck: true,
51
+ moduleResolution: "bundler",
52
+ allowImportingTsExtensions: true,
53
+ resolveJsonModule: true,
54
+ isolatedModules: true,
55
+ noEmit: true,
56
+ jsx: "react-jsx",
57
+ strict: true,
58
+ noUnusedLocals: true,
59
+ noUnusedParameters: true,
60
+ noFallthroughCasesInSwitch: true,
61
+ baseUrl: ".",
62
+ paths: {
63
+ "@/*": ["./src/*"],
64
+ },
65
+ },
66
+ include: ["src"],
67
+ };
68
+ await fsp.writeFile(
69
+ tsconfigAppPath,
70
+ JSON.stringify(config, null, 2) + "\n",
71
+ "utf8"
72
+ );
73
+ }
74
+
75
+ // Create/update root tsconfig.json with references
76
+ const rootTsconfigPath = path.join(projectPath, "tsconfig.json");
77
+ const rootTsconfig = {
78
+ files: [],
79
+ references: [{ path: "./tsconfig.app.json" }],
80
+ };
81
+ await fsp.writeFile(
82
+ rootTsconfigPath,
83
+ JSON.stringify(rootTsconfig, null, 2) + "\n",
84
+ "utf8"
85
+ );
86
+ }
87
+
88
+ async function configureJsconfig(projectPath) {
89
+ const jsconfigPath = path.join(projectPath, "jsconfig.json");
90
+ const config = {
91
+ compilerOptions: {
92
+ baseUrl: ".",
93
+ paths: {
94
+ "@/*": ["./src/*"],
95
+ },
96
+ },
97
+ include: ["src"],
98
+ };
99
+ await fsp.writeFile(jsconfigPath, JSON.stringify(config, null, 2) + "\n", "utf8");
100
+ }
101
+
102
+ async function addTsconfigPathsToViteConfig(projectPath, ext) {
103
+ const viteConfigPath = path.join(projectPath, `vite.config.${ext}`);
104
+ try {
105
+ let content = await readFileContent(viteConfigPath);
106
+
107
+ if (content.includes("tsconfigPaths")) return;
108
+
109
+ const lines = content.split("\n");
110
+
111
+ // Find where imports end (last import line index)
112
+ let lastImportIdx = -1;
113
+ for (let i = 0; i < lines.length; i++) {
114
+ if (lines[i].startsWith("import ")) lastImportIdx = i;
115
+ }
116
+
117
+ // Insert path/fileURLToPath imports after last import
118
+ const newImports = [
119
+ 'import path from "path";',
120
+ 'import { fileURLToPath } from "url";',
121
+ "const __dirname = path.dirname(fileURLToPath(import.meta.url));",
122
+ ];
123
+ lines.splice(lastImportIdx + 1, 0, ...newImports);
124
+
125
+ let result = lines.join("\n");
126
+
127
+ if (result.includes("resolve:")) {
128
+ result = result.replace(
129
+ /resolve:\s*\{/,
130
+ 'resolve: {\n tsconfigPaths: true,\n alias: [{ find: "@", replacement: path.resolve(__dirname, "src") }],'
131
+ );
132
+ } else {
133
+ result = result.replace(
134
+ /defineConfig\(\{/,
135
+ 'defineConfig({\n resolve: {\n tsconfigPaths: true,\n alias: [{ find: "@", replacement: path.resolve(__dirname, "src") }],\n },'
136
+ );
137
+ }
138
+
139
+ await fsp.writeFile(viteConfigPath, result, "utf8");
140
+ } catch {
141
+ // file doesn't exist, skip
142
+ }
143
+ }
144
+
145
+ async function configurePackageJson(projectPath) {
146
+ const pkgPath = path.join(projectPath, "package.json");
147
+ try {
148
+ const content = await readFileContent(pkgPath);
149
+ const pkg = JSON.parse(content);
150
+
151
+ if (!pkg.scripts) pkg.scripts = {};
152
+
153
+ pkg.scripts.dev = "vite";
154
+ pkg.scripts.build = "vite build";
155
+ pkg.scripts.preview = "vite preview";
156
+
157
+ await fsp.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
158
+ } catch {
159
+ // file doesn't exist, skip
160
+ }
161
+ }
@@ -0,0 +1,45 @@
1
+ import { isCancel, select } from "@clack/prompts";
2
+ import { access, mkdir, readdir, rm } from "fs/promises";
3
+ import path from "path";
4
+ import { onCancel } from "./prompts.js";
5
+
6
+ export async function confirmEmptyFolder(projectName) {
7
+ const cwd = process.cwd();
8
+ const targetPath = path.resolve(cwd, projectName || "");
9
+
10
+ try {
11
+ await access(targetPath);
12
+ } catch {
13
+ return targetPath;
14
+ }
15
+
16
+ const files = await readdir(targetPath);
17
+ if (files.length === 0) return targetPath;
18
+
19
+ const remove = await select({
20
+ message: `Folder "${projectName}" already exists and is not empty. How would you like to proceed?`,
21
+ options: [
22
+ { label: "Delete all files and continue", value: true },
23
+ { label: "Cancel setup", value: false },
24
+ ],
25
+ initialValue: true,
26
+ });
27
+
28
+ if (isCancel(remove) || !remove) {
29
+ onCancel();
30
+ }
31
+
32
+ if (targetPath === cwd) {
33
+ const inside = await readdir(targetPath);
34
+ await Promise.all(
35
+ inside.map((name) =>
36
+ rm(path.join(targetPath, name), { recursive: true, force: true })
37
+ )
38
+ );
39
+ } else {
40
+ await rm(targetPath, { recursive: true, force: true });
41
+ await mkdir(targetPath);
42
+ }
43
+
44
+ return targetPath;
45
+ }
package/src/css.js ADDED
@@ -0,0 +1,59 @@
1
+ import fs from "fs";
2
+ import { promises as fsp } from "fs";
3
+ import path from "path";
4
+ import { deleteIfExists } from "./utils/fs.js";
5
+
6
+ export async function setupCssFramework({
7
+ projectPath,
8
+ templatesDir,
9
+ language,
10
+ cssFramework,
11
+ ext,
12
+ pkg,
13
+ }) {
14
+ process.chdir(projectPath);
15
+ const cssDir = path.join(templatesDir, "css");
16
+ const indexCssPath = path.join(projectPath, "src", "index.css");
17
+
18
+ if (cssFramework === "tailwind") {
19
+ const viteConfigFile = `vite.config.${language === "ts" ? "ts" : "js"}`;
20
+ const viteConfigTarget = path.join(projectPath, viteConfigFile);
21
+ const viteConfigContent = await fsp.readFile(
22
+ path.join(cssDir, "tailwind", viteConfigFile),
23
+ "utf8"
24
+ );
25
+ await fsp.writeFile(viteConfigTarget, viteConfigContent, "utf8");
26
+
27
+ const indexCssContent = await fsp.readFile(
28
+ path.join(cssDir, "tailwind", "src", "index.css"),
29
+ "utf8"
30
+ );
31
+ await fsp.writeFile(indexCssPath, indexCssContent, "utf8");
32
+ } else if (cssFramework === "bootstrap") {
33
+ const mainFile = path.join(projectPath, "src", `main.${ext}`);
34
+ if (fs.existsSync(mainFile)) {
35
+ let mainContent = await fsp.readFile(mainFile, "utf8");
36
+ if (
37
+ !mainContent.includes("bootstrap/dist/css/bootstrap.min.css")
38
+ ) {
39
+ mainContent =
40
+ "import 'bootstrap/dist/css/bootstrap.min.css';\n" + mainContent;
41
+ await fsp.writeFile(mainFile, mainContent, "utf8");
42
+ }
43
+ }
44
+ const indexCssContent = await fsp.readFile(
45
+ path.join(cssDir, "bootstrap", "src", "index.css"),
46
+ "utf8"
47
+ );
48
+ await fsp.writeFile(indexCssPath, indexCssContent, "utf8");
49
+ } else {
50
+ const indexCssContent = await fsp.readFile(
51
+ path.join(cssDir, "none", "src", "index.css"),
52
+ "utf8"
53
+ );
54
+ await fsp.writeFile(indexCssPath, indexCssContent, "utf8");
55
+ }
56
+
57
+ // Clean up Vite's default App.css
58
+ await deleteIfExists(path.join(projectPath, "src", "App.css"));
59
+ }
@@ -0,0 +1,101 @@
1
+ import { execa } from "execa";
2
+
3
+ export async function installDeps(pkgManager, dev, packages, cwd) {
4
+ let cmd, args;
5
+ switch (pkgManager) {
6
+ case "pnpm":
7
+ cmd = "pnpm";
8
+ args = dev ? ["add", "-D", ...packages] : ["add", ...packages];
9
+ break;
10
+ case "yarn":
11
+ cmd = "yarn";
12
+ args = dev ? ["add", "-D", ...packages] : ["add", ...packages];
13
+ break;
14
+ case "bun":
15
+ cmd = "bun";
16
+ args = dev ? ["add", "-d", ...packages] : ["add", ...packages];
17
+ break;
18
+ default:
19
+ cmd = "npm";
20
+ args = dev
21
+ ? ["install", "-D", ...packages]
22
+ : ["install", ...packages];
23
+ }
24
+ await execa(cmd, args, { stdio: "pipe", cwd });
25
+ }
26
+
27
+ export async function installAllDeps(responses, pkg, cwd) {
28
+ const deps = [];
29
+ const devDeps = [];
30
+
31
+ // CSS framework
32
+ if (responses.cssFramework === "tailwind") {
33
+ devDeps.push("tailwindcss", "@tailwindcss/vite");
34
+ deps.push("clsx", "tailwind-merge");
35
+ } else if (responses.cssFramework === "bootstrap") {
36
+ deps.push("bootstrap", "react-bootstrap");
37
+ }
38
+
39
+ // State management
40
+ if (responses.stateManagement === "redux") {
41
+ deps.push("@reduxjs/toolkit", "react-redux");
42
+ } else if (responses.stateManagement === "zustand") {
43
+ deps.push("zustand");
44
+ }
45
+
46
+ // Router
47
+ if (responses.router) {
48
+ deps.push("react-router-dom");
49
+ }
50
+
51
+ // Icons
52
+ if (responses.iconLibrary === "lucide") {
53
+ deps.push("lucide-react");
54
+ } else if (responses.iconLibrary === "huge") {
55
+ deps.push("@hugeicons/react", "@hugeicons/core-free-icons");
56
+ }
57
+
58
+ // Axios
59
+ if (responses.axios) {
60
+ deps.push("axios");
61
+ }
62
+
63
+ // Testing
64
+ if (responses.testing === "vitest") {
65
+ devDeps.push(
66
+ "vitest",
67
+ "@testing-library/react",
68
+ "@testing-library/jest-dom",
69
+ "jsdom"
70
+ );
71
+ } else if (responses.testing === "jest") {
72
+ devDeps.push(
73
+ "jest",
74
+ "ts-jest",
75
+ "@types/jest",
76
+ "@testing-library/react",
77
+ "@testing-library/jest-dom",
78
+ "jsdom"
79
+ );
80
+ }
81
+
82
+ // Linter
83
+ if (responses.linter === "eslint") {
84
+ devDeps.push("eslint", "@eslint/js", "eslint-plugin-react-hooks", "eslint-plugin-react-refresh", "globals");
85
+ if (responses.language === "ts") {
86
+ devDeps.push("typescript-eslint");
87
+ }
88
+ } else if (responses.linter === "oxlint") {
89
+ devDeps.push("oxlint");
90
+ }
91
+
92
+ // Always re-install react + react-dom for consistency
93
+ deps.push("react", "react-dom");
94
+
95
+ if (deps.length > 0) {
96
+ await installDeps(pkg, false, deps, cwd);
97
+ }
98
+ if (devDeps.length > 0) {
99
+ await installDeps(pkg, true, devDeps, cwd);
100
+ }
101
+ }