@aditokmo/react-cli-setup 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 +120 -0
- package/dist/index.js +81 -0
- package/dist/installers.js +23 -0
- package/dist/mapper.js +17 -0
- package/dist/packages.js +60 -0
- package/dist/questions.js +82 -0
- package/dist/types.js +1 -0
- package/dist/utils.js +74 -0
- package/package.json +62 -0
- package/templates/base/README.md +73 -0
- package/templates/base/eslint.config.js +23 -0
- package/templates/base/index.html +14 -0
- package/templates/base/package.json +30 -0
- package/templates/base/pnpm-lock.yaml +2105 -0
- package/templates/base/public/vite.svg +1 -0
- package/templates/base/src/App.tsx +9 -0
- package/templates/base/src/api/api.ts +29 -0
- package/templates/base/src/api/http.ts +27 -0
- package/templates/base/src/api/index.ts +2 -0
- package/templates/base/src/assets/react.svg +1 -0
- package/templates/base/src/layout/AuthLayout.tsx +0 -0
- package/templates/base/src/layout/MainLayout.tsx +0 -0
- package/templates/base/src/layout/index.ts +2 -0
- package/templates/base/src/main.tsx +9 -0
- package/templates/base/src/modules/auth/components/AuthForm.tsx +5 -0
- package/templates/base/src/modules/auth/hooks/useAuth.ts +43 -0
- package/templates/base/src/modules/auth/pages/ForgotPassword.tsx +5 -0
- package/templates/base/src/modules/auth/pages/Login.tsx +5 -0
- package/templates/base/src/modules/auth/pages/Register.tsx +5 -0
- package/templates/base/src/modules/auth/pages/index.ts +3 -0
- package/templates/base/src/modules/auth/services/auth.service.ts +17 -0
- package/templates/base/src/modules/auth/services/endpoint.ts +10 -0
- package/templates/base/src/modules/auth/services/index.ts +3 -0
- package/templates/base/src/modules/auth/services/oauth.service.ts +1 -0
- package/templates/base/src/modules/auth/services/password.service.ts +1 -0
- package/templates/base/src/modules/auth/types/auth.types.ts +3 -0
- package/templates/base/src/modules/auth/types/index.ts +3 -0
- package/templates/base/src/modules/auth/types/oauth.types.ts +1 -0
- package/templates/base/src/modules/auth/types/password.types.ts +1 -0
- package/templates/base/src/modules/common/pages/NotFound.tsx +9 -0
- package/templates/base/src/modules/common/pages/index.ts +1 -0
- package/templates/base/src/utils/api-error-handler.ts +25 -0
- package/templates/base/tsconfig.app.json +32 -0
- package/templates/base/tsconfig.json +13 -0
- package/templates/base/tsconfig.node.json +26 -0
- package/templates/base/vite-env.d.ts +1 -0
- package/templates/base/vite.config.ts +18 -0
- package/templates/hooks/index.ts +0 -0
- package/templates/hooks/useDebounce.ts +1 -0
- package/templates/hooks/useTheme.ts +1 -0
- package/templates/hooks/useThrottle.ts +1 -0
- package/templates/hooks/useWebStorage.ts +1 -0
- package/templates/router/react-router/src/components/ProtectedRoute.tsx +20 -0
- package/templates/router/react-router/src/components/PublicRoute.tsx +20 -0
- package/templates/router/react-router/src/modules/auth/routes/index.tsx +21 -0
- package/templates/router/react-router/src/routes/AppRoutes.tsx +18 -0
- package/templates/router/react-router/src/routes/index.ts +3 -0
- package/templates/router/tanstack-router/src/providers/TanstackRouterProvider.tsx +14 -0
- package/templates/router/tanstack-router/src/routes/__root.tsx +13 -0
- package/templates/router/tanstack-router/src/routes/_protected/index.tsx +10 -0
- package/templates/router/tanstack-router/src/routes/_protected.tsx +18 -0
- package/templates/router/tanstack-router/src/routes/_public/forgot-password.tsx +6 -0
- package/templates/router/tanstack-router/src/routes/_public/login.tsx +6 -0
- package/templates/router/tanstack-router/src/routes/_public/register.tsx +6 -0
- package/templates/router/tanstack-router/src/routes/_public.tsx +13 -0
- package/templates/state/react-query/src/ReactQueryProvider.tsx +15 -0
- package/templates/state/react-query/src/index.ts +1 -0
- package/templates/state/zustand/src/auth/useAuthStore.ts +29 -0
- package/templates/state/zustand/src/index.ts +2 -0
- package/templates/state/zustand/src/theme/useThemeStore.ts +27 -0
- package/templates/styles/css/src/main.css +0 -0
- package/templates/styles/scss/src/main.scss +0 -0
- package/templates/styles/tailwind/config/_vite.config.ts +14 -0
- package/templates/styles/tailwind/src/main.css +123 -0
- package/templates/ui/shadcn/src/components/ui/button.tsx +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# @aditokmo/react-cli-setup 🚀
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@aditokmo/react-cli-setup)
|
|
4
|
+
|
|
5
|
+
A React CLI built with Vite that helps you build and structure your projects in seconds. It eliminates manual setup by configuring your favorite tools into a **clean, modular architecture** automatically.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
* **Automated Installation:** Installs all selected libraries (listed below) for you.
|
|
10
|
+
* **Structuring:** Automatically generates a scalable folder structure based on your choices.
|
|
11
|
+
* **Boilerplate Injection:** Pre-configures Providers, Router paths, etc., so you can start coding features immediately
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
Run the following command in your terminal to start CLI
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Using NPM
|
|
21
|
+
npx @aditokmo/react-cli-setup
|
|
22
|
+
|
|
23
|
+
# Using PNPM
|
|
24
|
+
pnpm dlx @aditokmo/react-cli-setup
|
|
25
|
+
|
|
26
|
+
# Using Yarn
|
|
27
|
+
yarn dlx @aditokmo/react-cli-setup
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
| Category | Options |
|
|
35
|
+
| :--- | :--- |
|
|
36
|
+
| **Folder Structure** | Feature-based |
|
|
37
|
+
| **Modules** | Common, Auth |
|
|
38
|
+
| **Routing** | React Router, TanStack Router |
|
|
39
|
+
| **Data Fetching** | TanStack Query (React Query) & Axios |
|
|
40
|
+
| **State Management** | Zustand |
|
|
41
|
+
| **Form** | React Hook Form, TanStack Form |
|
|
42
|
+
| **Schema** | Zod, Yup |
|
|
43
|
+
| **Styling** | CSS, SCSS, Tailwind CSS |
|
|
44
|
+
| **UI Components** | Shadcn |
|
|
45
|
+
| **Icons** | React Icons, Font Awesome |
|
|
46
|
+
| **Toast** | React Toastify, React Hot Toast, Sonner |
|
|
47
|
+
| **Custom Hooks** | |
|
|
48
|
+
| **Helpers** | |
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Folder Structure
|
|
53
|
+
|
|
54
|
+
Note: This is the complete folder structure. The actual folders generated will depend on the libraries and options you select during the setup process.
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
src/
|
|
58
|
+
├── api/ # Global API client & Axios config
|
|
59
|
+
├── components/ # Shared UI components
|
|
60
|
+
├── hooks/ # Global reusable custom hooks
|
|
61
|
+
├── providers/ # Providers (React Query, Tanstack Router)
|
|
62
|
+
├── routes/ # Route definitions
|
|
63
|
+
├── styles/ # Styles (Tailwind, CSS, SCSS)
|
|
64
|
+
├── layout/ # Layout to split protected and unprotected routes
|
|
65
|
+
│ ├── MainLayout.tsx
|
|
66
|
+
│ ├── AuthLayout.tsx
|
|
67
|
+
├── modules/ # Feature-based modules (The core of your app)
|
|
68
|
+
│ └── auth/ # Example: Auth module
|
|
69
|
+
│ ├── components/
|
|
70
|
+
│ ├── hooks/
|
|
71
|
+
│ ├── pages/
|
|
72
|
+
│ ├── services/
|
|
73
|
+
│ └── types/
|
|
74
|
+
├── store/ # Global State Managemenet (Zustand)
|
|
75
|
+
│ ├── useAuthStore.ts
|
|
76
|
+
│ └── useThemeStore.ts
|
|
77
|
+
└── utils/ # Helper functions
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Local Setup
|
|
83
|
+
|
|
84
|
+
To do your own changes and use this CLI locally:
|
|
85
|
+
|
|
86
|
+
1. **Clone the repository:**
|
|
87
|
+
```bash
|
|
88
|
+
git clone https://github.com/aditokmo/react-cli.git
|
|
89
|
+
cd react-cli
|
|
90
|
+
|
|
91
|
+
2. **Install dependencies:**
|
|
92
|
+
```bash
|
|
93
|
+
pnpm install
|
|
94
|
+
|
|
95
|
+
3. **Build the project:**
|
|
96
|
+
```bash
|
|
97
|
+
pnpm run build
|
|
98
|
+
|
|
99
|
+
4. **Link globally:**
|
|
100
|
+
```bash
|
|
101
|
+
pnpm link --global
|
|
102
|
+
|
|
103
|
+
5. **Run command in your terminal to start CLI locally:**
|
|
104
|
+
```bash
|
|
105
|
+
react-cli-setup
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
## Project Structure
|
|
112
|
+
|
|
113
|
+
* `cli/` - Logic for the CLI.
|
|
114
|
+
* `templates/` - Pre-defined boilerplates and configurations.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
<p align="center">
|
|
119
|
+
Developed with ❤️ by <a href="https://github.com/aditokmo">aditokmo</a>
|
|
120
|
+
</p>
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { askQuestions } from './questions.js';
|
|
6
|
+
import { copyTemplate, patchViteConfig, finalizeViteConfig, patchAppFile, finalizeAppFile } from './utils.js';
|
|
7
|
+
import { collectDependencies } from './installers.js';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
async function main() {
|
|
12
|
+
console.log('⚛️ Welcome to React CLI Setup by github/aditokmo');
|
|
13
|
+
let projectDir = '';
|
|
14
|
+
try {
|
|
15
|
+
const answers = await askQuestions();
|
|
16
|
+
projectDir = path.join(process.cwd(), answers.projectName);
|
|
17
|
+
const templateRoot = path.join(__dirname, '../templates');
|
|
18
|
+
const appFilePath = path.join(projectDir, 'src', 'App.tsx');
|
|
19
|
+
const viteConfigPath = path.join(projectDir, 'vite.config.ts');
|
|
20
|
+
fs.ensureDirSync(projectDir);
|
|
21
|
+
copyTemplate(path.join(templateRoot, 'base'), projectDir);
|
|
22
|
+
// Styles
|
|
23
|
+
if (answers.style === 'tailwind') {
|
|
24
|
+
copyTemplate(path.join(templateRoot, 'styles', 'tailwind', 'src'), path.join(projectDir, 'src/styles'));
|
|
25
|
+
patchViteConfig(viteConfigPath, false, 'import tailwindcss from "@tailwindcss/vite"', 'tailwindcss()');
|
|
26
|
+
}
|
|
27
|
+
if (answers.style === 'css') {
|
|
28
|
+
copyTemplate(path.join(templateRoot, 'styles', 'css', 'src'), path.join(projectDir, 'src/styles'));
|
|
29
|
+
}
|
|
30
|
+
// State Management
|
|
31
|
+
if (answers.reactQuery) {
|
|
32
|
+
copyTemplate(path.join(templateRoot, 'state', 'react-query', 'src'), path.join(projectDir, 'src/providers'));
|
|
33
|
+
patchAppFile(appFilePath, "import { ReactQueryProvider } from './providers'", "<ReactQueryProvider>", "</ReactQueryProvider>");
|
|
34
|
+
}
|
|
35
|
+
if (answers.globalState === 'zustand') {
|
|
36
|
+
copyTemplate(path.join(templateRoot, 'state', 'zustand', 'src'), path.join(projectDir, 'src/store'));
|
|
37
|
+
}
|
|
38
|
+
// Router
|
|
39
|
+
if (answers.router === 'react-router') {
|
|
40
|
+
copyTemplate(path.join(templateRoot, 'router', 'react-router', 'src', 'routes'), path.join(projectDir, 'src/routes'));
|
|
41
|
+
copyTemplate(path.join(templateRoot, 'router', 'react-router', 'src', 'components'), path.join(projectDir, 'src/components'));
|
|
42
|
+
copyTemplate(path.join(templateRoot, 'router', 'react-router', 'src', 'modules', 'auth', 'routes'), path.join(projectDir, 'src/modules/auth/routes'));
|
|
43
|
+
patchAppFile(appFilePath, "import { BrowserRouter } from 'react-router'\nimport { AppRoutes } from './routes'", "<BrowserRouter>\n <AppRoutes />", "</BrowserRouter>");
|
|
44
|
+
}
|
|
45
|
+
else if (answers.router === 'tanstack-router') {
|
|
46
|
+
copyTemplate(path.join(templateRoot, 'router', 'tanstack-router', 'src', 'routes'), path.join(projectDir, 'src/routes'));
|
|
47
|
+
copyTemplate(path.join(templateRoot, 'router', 'tanstack-router', 'src', 'providers'), path.join(projectDir, 'src/providers'));
|
|
48
|
+
patchAppFile(appFilePath, "import { TanStackRouterProvider } from './providers/TanstackRouterProvider'", "<TanStackRouterProvider />", "");
|
|
49
|
+
patchViteConfig(viteConfigPath, true, "import { tanstackRouter } from '@tanstack/router-plugin/vite'", "tanstackRouter({ target: 'react', autoCodeSplitting: true })");
|
|
50
|
+
}
|
|
51
|
+
finalizeAppFile(appFilePath);
|
|
52
|
+
finalizeViteConfig(viteConfigPath);
|
|
53
|
+
const { dependency, devDependency, cmd } = collectDependencies(answers);
|
|
54
|
+
process.chdir(projectDir);
|
|
55
|
+
if (dependency.length) {
|
|
56
|
+
console.log('📦 Installing dependencies...');
|
|
57
|
+
execSync(`pnpm add ${dependency.join(' ')}`, { stdio: 'inherit' });
|
|
58
|
+
}
|
|
59
|
+
if (devDependency.length) {
|
|
60
|
+
console.log('📦 Installing dev dependencies...');
|
|
61
|
+
execSync(`pnpm add -D ${devDependency.join(' ')}`, { stdio: 'inherit' });
|
|
62
|
+
}
|
|
63
|
+
// Extra cmds like shadcn
|
|
64
|
+
for (const command of cmd) {
|
|
65
|
+
console.log(`⚙️ Running: ${command}`);
|
|
66
|
+
execSync(command, { stdio: 'inherit' });
|
|
67
|
+
}
|
|
68
|
+
console.log('✅ Project setup completed');
|
|
69
|
+
console.log(`👉 cd ${answers.projectName} && pnpm run dev`);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
console.error('\n❌ Error while creating a project:');
|
|
73
|
+
console.error(`👉 ${error.message}`);
|
|
74
|
+
if (projectDir && fs.existsSync(projectDir)) {
|
|
75
|
+
console.log('🧹 Cleaning... Deleting failed project installation.');
|
|
76
|
+
fs.removeSync(projectDir);
|
|
77
|
+
}
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
main();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { installers } from './mapper.js';
|
|
2
|
+
import { axiosInstaller, shadcnInstaller } from './packages.js';
|
|
3
|
+
export function collectDependencies(answers) {
|
|
4
|
+
const dependency = new Set();
|
|
5
|
+
const devDependency = new Set();
|
|
6
|
+
const cmd = [];
|
|
7
|
+
// Default base packages
|
|
8
|
+
axiosInstaller.dependency?.forEach(d => dependency.add(d));
|
|
9
|
+
// Packages from answers
|
|
10
|
+
Object.entries(answers).forEach(([key, value]) => {
|
|
11
|
+
const installer = installers[value === true ? key : value];
|
|
12
|
+
installer?.dependency?.forEach(d => dependency.add(d));
|
|
13
|
+
installer?.devDependency?.forEach(d => devDependency.add(d));
|
|
14
|
+
});
|
|
15
|
+
if (answers.shadcn && answers.style === 'tailwind') {
|
|
16
|
+
cmd.push(...shadcnInstaller.cmd);
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
dependency: Array.from(dependency),
|
|
20
|
+
devDependency: Array.from(devDependency),
|
|
21
|
+
cmd
|
|
22
|
+
};
|
|
23
|
+
}
|
package/dist/mapper.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { fontAwesomeIconsInstaller, reactFormHookInstaller, reactHotToastInstaller, reactIconsInstaller, reactQueryInstaller, reactRouterInstaller, reactToastifyInstaller, sonnerInstaller, tailwindInstaller, tanstackFormInstaller, tanstackRouterInstaller, yupInstaller, zodInstaller, zustandInstaller } from './packages.js';
|
|
2
|
+
export const installers = {
|
|
3
|
+
'reactQuery': reactQueryInstaller,
|
|
4
|
+
'zustand': zustandInstaller,
|
|
5
|
+
'react-router': reactRouterInstaller,
|
|
6
|
+
'tanstack-router': tanstackRouterInstaller,
|
|
7
|
+
'tailwind': tailwindInstaller,
|
|
8
|
+
'react-icons': reactIconsInstaller,
|
|
9
|
+
'font-awesome': fontAwesomeIconsInstaller,
|
|
10
|
+
'react-hot-toast': reactHotToastInstaller,
|
|
11
|
+
'react-toastify': reactToastifyInstaller,
|
|
12
|
+
'sonner': sonnerInstaller,
|
|
13
|
+
'react-hook-form': reactFormHookInstaller,
|
|
14
|
+
'tanstack-form': tanstackFormInstaller,
|
|
15
|
+
'zod': zodInstaller,
|
|
16
|
+
'yup': yupInstaller,
|
|
17
|
+
};
|
package/dist/packages.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// React Query
|
|
2
|
+
export const reactQueryInstaller = {
|
|
3
|
+
dependency: ['@tanstack/react-query'],
|
|
4
|
+
devDependency: ['@tanstack/react-query-devtools']
|
|
5
|
+
};
|
|
6
|
+
// Global State Management
|
|
7
|
+
export const zustandInstaller = {
|
|
8
|
+
dependency: ['zustand'],
|
|
9
|
+
};
|
|
10
|
+
// Styles
|
|
11
|
+
export const tailwindInstaller = {
|
|
12
|
+
dependency: ['tailwindcss', '@tailwindcss/vite'],
|
|
13
|
+
};
|
|
14
|
+
// Icons
|
|
15
|
+
export const reactIconsInstaller = {
|
|
16
|
+
dependency: ['react-icons'],
|
|
17
|
+
};
|
|
18
|
+
export const fontAwesomeIconsInstaller = {
|
|
19
|
+
dependency: ['@fortawesome/fontawesome-svg-core', '@fortawesome/react-fontawesome', '@fortawesome/free-solid-svg-icons', '@fortawesome/free-regular-svg-icons', '@fortawesome/free-brands-svg-icons'],
|
|
20
|
+
};
|
|
21
|
+
// Toast
|
|
22
|
+
export const reactHotToastInstaller = {
|
|
23
|
+
dependency: ['react-hot-toast'],
|
|
24
|
+
};
|
|
25
|
+
export const reactToastifyInstaller = {
|
|
26
|
+
dependency: ['react-toastify'],
|
|
27
|
+
};
|
|
28
|
+
export const sonnerInstaller = {
|
|
29
|
+
dependency: ['sonner'],
|
|
30
|
+
};
|
|
31
|
+
// UI Libs
|
|
32
|
+
export const shadcnInstaller = {
|
|
33
|
+
cmd: ['pnpm dlx shadcn@latest init']
|
|
34
|
+
};
|
|
35
|
+
// Routers
|
|
36
|
+
export const reactRouterInstaller = {
|
|
37
|
+
dependency: ['react-router', 'react-router-dom'],
|
|
38
|
+
};
|
|
39
|
+
export const tanstackRouterInstaller = {
|
|
40
|
+
dependency: ['@tanstack/react-router'],
|
|
41
|
+
devDependency: ['@tanstack/react-router-devtools', '@tanstack/router-plugin'],
|
|
42
|
+
};
|
|
43
|
+
// Forms
|
|
44
|
+
export const reactFormHookInstaller = {
|
|
45
|
+
dependency: ['react-hook-form'],
|
|
46
|
+
};
|
|
47
|
+
export const tanstackFormInstaller = {
|
|
48
|
+
dependency: ['@tanstack/react-form'],
|
|
49
|
+
};
|
|
50
|
+
// Schemas
|
|
51
|
+
export const zodInstaller = {
|
|
52
|
+
dependency: ['zod', '@hookform/resolvers'],
|
|
53
|
+
};
|
|
54
|
+
export const yupInstaller = {
|
|
55
|
+
dependency: ['yup', '@hookform/resolvers'],
|
|
56
|
+
};
|
|
57
|
+
// Base installation
|
|
58
|
+
export const axiosInstaller = {
|
|
59
|
+
dependency: ['axios'],
|
|
60
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { group, text, select, confirm, isCancel, cancel } from '@clack/prompts';
|
|
2
|
+
export async function askQuestions() {
|
|
3
|
+
const results = await group({
|
|
4
|
+
projectName: () => text({
|
|
5
|
+
message: 'Project name:',
|
|
6
|
+
placeholder: 'my-app',
|
|
7
|
+
validate: (value) => (value.length < 0 ? 'Project name is required' : undefined),
|
|
8
|
+
}),
|
|
9
|
+
style: () => select({
|
|
10
|
+
message: 'Choose styling:',
|
|
11
|
+
options: [
|
|
12
|
+
{ value: 'tailwind', label: 'Tailwind' },
|
|
13
|
+
{ value: 'css', label: 'CSS' },
|
|
14
|
+
],
|
|
15
|
+
}),
|
|
16
|
+
shadcn: ({ results }) => {
|
|
17
|
+
if (results.style !== 'tailwind')
|
|
18
|
+
return Promise.resolve(false);
|
|
19
|
+
return confirm({ message: 'Include Shadcn UI?' });
|
|
20
|
+
},
|
|
21
|
+
icons: () => select({
|
|
22
|
+
message: 'Choose icon library:',
|
|
23
|
+
options: [
|
|
24
|
+
{ value: 'react-icons', label: 'React Icons' },
|
|
25
|
+
{ value: 'font-awesome', label: 'Font Awesome' },
|
|
26
|
+
]
|
|
27
|
+
}),
|
|
28
|
+
toast: () => select({
|
|
29
|
+
message: 'Choose toast library:',
|
|
30
|
+
options: [
|
|
31
|
+
{ value: 'react-hot-toast', label: 'React Hot Toast' },
|
|
32
|
+
{ value: 'react-toastify', label: 'React Toastify' },
|
|
33
|
+
{ value: 'sonner', label: 'Sonner' },
|
|
34
|
+
]
|
|
35
|
+
}),
|
|
36
|
+
router: () => select({
|
|
37
|
+
message: 'Choose router:',
|
|
38
|
+
options: [
|
|
39
|
+
{ value: 'react-router', label: 'React Router' },
|
|
40
|
+
{ value: 'tanstack-router', label: 'Tanstack Router' },
|
|
41
|
+
],
|
|
42
|
+
}),
|
|
43
|
+
form: () => select({
|
|
44
|
+
message: 'Choose form library:',
|
|
45
|
+
options: [
|
|
46
|
+
{ value: 'react-hook-form', label: 'React Hook Form' },
|
|
47
|
+
{ value: 'tanstack-form', label: 'TanStack Form' },
|
|
48
|
+
],
|
|
49
|
+
}),
|
|
50
|
+
schema: () => select({
|
|
51
|
+
message: 'Choose schema library:',
|
|
52
|
+
options: [
|
|
53
|
+
{ value: 'zod', label: 'Zod' },
|
|
54
|
+
{ value: 'yup', label: 'Yup' },
|
|
55
|
+
]
|
|
56
|
+
}),
|
|
57
|
+
globalState: () => select({
|
|
58
|
+
message: 'Choose global state management library:',
|
|
59
|
+
options: [
|
|
60
|
+
{ value: 'zustand', label: 'Zustand' },
|
|
61
|
+
]
|
|
62
|
+
}),
|
|
63
|
+
reactQuery: () => confirm({ message: 'Include React Query?' }),
|
|
64
|
+
}, {
|
|
65
|
+
onCancel: () => {
|
|
66
|
+
cancel('Operation cancelled.');
|
|
67
|
+
process.exit(0);
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
if (isCancel(results.projectName) ||
|
|
71
|
+
isCancel(results.style) ||
|
|
72
|
+
isCancel(results.router) ||
|
|
73
|
+
isCancel(results.shadcn) ||
|
|
74
|
+
isCancel(results.reactQuery)) {
|
|
75
|
+
cancel('Setup cancelled.');
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
...results,
|
|
80
|
+
shadcn: results.shadcn,
|
|
81
|
+
};
|
|
82
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
export function copyTemplate(src, path) {
|
|
3
|
+
if (!fs.existsSync(src)) {
|
|
4
|
+
console.warn(`⚠️ Template folder ${src} dosn't exist`);
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
fs.copySync(src, path, { overwrite: true });
|
|
8
|
+
}
|
|
9
|
+
export function patchAppFile(filePath, importLine, openTag, closeTag) {
|
|
10
|
+
if (!fs.existsSync(filePath))
|
|
11
|
+
return;
|
|
12
|
+
let content = fs.readFileSync(filePath, 'utf-8');
|
|
13
|
+
if (importLine) {
|
|
14
|
+
content = content.replace('// [IMPORTS]', `${importLine}\n// [IMPORTS]`);
|
|
15
|
+
}
|
|
16
|
+
if (openTag) {
|
|
17
|
+
if (content.includes('// [PROVIDERS]')) {
|
|
18
|
+
const initialContent = closeTag
|
|
19
|
+
? `${openTag}\n // [PROVIDERS]\n ${closeTag}`
|
|
20
|
+
: openTag;
|
|
21
|
+
content = content.replace('// [PROVIDERS]', initialContent);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
content = content.replace(/return \(([\s\S]*?)\)/, (match, inner) => {
|
|
25
|
+
return `return (\n ${openTag}${inner.trim()}\n ${closeTag}\n )`;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
fs.writeFileSync(filePath, content);
|
|
30
|
+
}
|
|
31
|
+
export function patchViteConfig(filePath, beforeReactPlugin, importLine, pluginLine) {
|
|
32
|
+
if (!fs.existsSync(filePath))
|
|
33
|
+
return;
|
|
34
|
+
let content = fs.readFileSync(filePath, 'utf-8');
|
|
35
|
+
if (importLine && !content.includes(importLine)) {
|
|
36
|
+
content = content.replace('// [IMPORTS]', `${importLine}\n// [IMPORTS]`);
|
|
37
|
+
}
|
|
38
|
+
if (beforeReactPlugin) {
|
|
39
|
+
if (pluginLine && !content.includes(pluginLine)) {
|
|
40
|
+
content = content.replace('// [BEFORE_REACT_PLUGINS]', `${pluginLine},\n // [BEFORE_REACT_PLUGINS]`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
if (pluginLine && !content.includes(pluginLine)) {
|
|
45
|
+
content = content.replace('// [AFTER_REACT_PLUGINS]', `${pluginLine},\n // [AFTER_REACT_PLUGINS]`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
fs.writeFileSync(filePath, content);
|
|
49
|
+
}
|
|
50
|
+
export function finalizeViteConfig(filePath) {
|
|
51
|
+
if (!fs.existsSync(filePath))
|
|
52
|
+
return;
|
|
53
|
+
let content = fs.readFileSync(filePath, 'utf-8');
|
|
54
|
+
content = content
|
|
55
|
+
// Briše marker i sav prazan prostor (redove) oko njega
|
|
56
|
+
.replace(/\s*\/\/ \[IMPORTS\]\s*/g, '\n')
|
|
57
|
+
.replace(/\s*\/\/ \[AFTER_REACT_PLUGINS\]\s*/g, '\n')
|
|
58
|
+
.replace(/\s*\/\/ \[BEFORE_REACT_PLUGINS\]\s*/g, '\n')
|
|
59
|
+
// Sređuje višestruke prazne redove u jedan
|
|
60
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
61
|
+
.trim();
|
|
62
|
+
fs.writeFileSync(filePath, content + '\n');
|
|
63
|
+
}
|
|
64
|
+
export function finalizeAppFile(filePath) {
|
|
65
|
+
if (!fs.existsSync(filePath))
|
|
66
|
+
return;
|
|
67
|
+
let content = fs.readFileSync(filePath, 'utf-8');
|
|
68
|
+
content = content
|
|
69
|
+
.replace(/\s*\/\/ \[IMPORTS\]\s*/g, '\n')
|
|
70
|
+
.replace(/\s*\/\/ \[PROVIDERS\]\s*/g, '\n')
|
|
71
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
72
|
+
.trim();
|
|
73
|
+
fs.writeFileSync(filePath, content + '\n');
|
|
74
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aditokmo/react-cli-setup",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A fast React CLI to jumpstart your projects. It sets up your libraries and organizes a scalable folder structure so you can skip the configuration and go straight to coding.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"react-cli-setup": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"react",
|
|
12
|
+
"cli",
|
|
13
|
+
"vite",
|
|
14
|
+
"boilerplate",
|
|
15
|
+
"tanstack-router",
|
|
16
|
+
"tanstack-query",
|
|
17
|
+
"tanstack-form",
|
|
18
|
+
"zustand",
|
|
19
|
+
"shadcn-ui",
|
|
20
|
+
"tailwind",
|
|
21
|
+
"project-generator",
|
|
22
|
+
"react-hook-form",
|
|
23
|
+
"zod",
|
|
24
|
+
"enterprise-architecture",
|
|
25
|
+
"frontend-architecture",
|
|
26
|
+
"react-architecture"
|
|
27
|
+
],
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/aditokmo/react-cli-setup.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/aditokmo/react-cli-setup/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/aditokmo/react-cli-setup#readme",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"templates"
|
|
42
|
+
],
|
|
43
|
+
"author": "aditokmo",
|
|
44
|
+
"license": "ISC",
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@clack/prompts": "^0.11.0",
|
|
47
|
+
"chalk": "^5.6.2",
|
|
48
|
+
"fs-extra": "^11.3.3",
|
|
49
|
+
"ora": "^9.0.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/fs-extra": "^11.0.4",
|
|
53
|
+
"@types/node": "^25.0.3",
|
|
54
|
+
"tsx": "^4.19.2",
|
|
55
|
+
"typescript": "^5.9.3"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsc",
|
|
59
|
+
"start:cli": "tsx cli/index.ts",
|
|
60
|
+
"watch": "tsc --watch"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# React + TypeScript + Vite
|
|
2
|
+
|
|
3
|
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
4
|
+
|
|
5
|
+
Currently, two official plugins are available:
|
|
6
|
+
|
|
7
|
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
|
8
|
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
|
9
|
+
|
|
10
|
+
## React Compiler
|
|
11
|
+
|
|
12
|
+
The React Compiler is currently not compatible with SWC. See [this issue](https://github.com/vitejs/vite-plugin-react/issues/428) for tracking the progress.
|
|
13
|
+
|
|
14
|
+
## Expanding the ESLint configuration
|
|
15
|
+
|
|
16
|
+
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
export default defineConfig([
|
|
20
|
+
globalIgnores(['dist']),
|
|
21
|
+
{
|
|
22
|
+
files: ['**/*.{ts,tsx}'],
|
|
23
|
+
extends: [
|
|
24
|
+
// Other configs...
|
|
25
|
+
|
|
26
|
+
// Remove tseslint.configs.recommended and replace with this
|
|
27
|
+
tseslint.configs.recommendedTypeChecked,
|
|
28
|
+
// Alternatively, use this for stricter rules
|
|
29
|
+
tseslint.configs.strictTypeChecked,
|
|
30
|
+
// Optionally, add this for stylistic rules
|
|
31
|
+
tseslint.configs.stylisticTypeChecked,
|
|
32
|
+
|
|
33
|
+
// Other configs...
|
|
34
|
+
],
|
|
35
|
+
languageOptions: {
|
|
36
|
+
parserOptions: {
|
|
37
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
38
|
+
tsconfigRootDir: import.meta.dirname,
|
|
39
|
+
},
|
|
40
|
+
// other options...
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// eslint.config.js
|
|
50
|
+
import reactX from 'eslint-plugin-react-x'
|
|
51
|
+
import reactDom from 'eslint-plugin-react-dom'
|
|
52
|
+
|
|
53
|
+
export default defineConfig([
|
|
54
|
+
globalIgnores(['dist']),
|
|
55
|
+
{
|
|
56
|
+
files: ['**/*.{ts,tsx}'],
|
|
57
|
+
extends: [
|
|
58
|
+
// Other configs...
|
|
59
|
+
// Enable lint rules for React
|
|
60
|
+
reactX.configs['recommended-typescript'],
|
|
61
|
+
// Enable lint rules for React DOM
|
|
62
|
+
reactDom.configs.recommended,
|
|
63
|
+
],
|
|
64
|
+
languageOptions: {
|
|
65
|
+
parserOptions: {
|
|
66
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
67
|
+
tsconfigRootDir: import.meta.dirname,
|
|
68
|
+
},
|
|
69
|
+
// other options...
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
])
|
|
73
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import tseslint from 'typescript-eslint'
|
|
6
|
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
7
|
+
|
|
8
|
+
export default defineConfig([
|
|
9
|
+
globalIgnores(['dist']),
|
|
10
|
+
{
|
|
11
|
+
files: ['**/*.{ts,tsx}'],
|
|
12
|
+
extends: [
|
|
13
|
+
js.configs.recommended,
|
|
14
|
+
tseslint.configs.recommended,
|
|
15
|
+
reactHooks.configs.flat.recommended,
|
|
16
|
+
reactRefresh.configs.vite,
|
|
17
|
+
],
|
|
18
|
+
languageOptions: {
|
|
19
|
+
ecmaVersion: 2020,
|
|
20
|
+
globals: globals.browser,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
])
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<link rel="stylesheet" href="./src/styles/main.css" />
|
|
8
|
+
<title>vite-project</title>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div id="root"></div>
|
|
12
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-project",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"preview": "vite preview"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"react": "^19.2.0",
|
|
14
|
+
"react-dom": "^19.2.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@eslint/js": "^9.39.1",
|
|
18
|
+
"@types/node": "^24.10.1",
|
|
19
|
+
"@types/react": "^19.2.5",
|
|
20
|
+
"@types/react-dom": "^19.2.3",
|
|
21
|
+
"@vitejs/plugin-react-swc": "^4.2.2",
|
|
22
|
+
"eslint": "^9.39.1",
|
|
23
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
24
|
+
"eslint-plugin-react-refresh": "^0.4.24",
|
|
25
|
+
"globals": "^16.5.0",
|
|
26
|
+
"typescript": "~5.9.3",
|
|
27
|
+
"typescript-eslint": "^8.46.4",
|
|
28
|
+
"vite": "^7.2.4"
|
|
29
|
+
}
|
|
30
|
+
}
|