@aditokmo/create-react-project 0.2.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 (82) hide show
  1. package/README.md +150 -0
  2. package/dist/index.js +129 -0
  3. package/dist/installers.js +30 -0
  4. package/dist/mapper.js +28 -0
  5. package/dist/packages.js +62 -0
  6. package/dist/questions.js +123 -0
  7. package/dist/types.js +1 -0
  8. package/dist/utils.js +101 -0
  9. package/package.json +65 -0
  10. package/templates/base/README.md +73 -0
  11. package/templates/base/eslint.config.js +23 -0
  12. package/templates/base/index.html +15 -0
  13. package/templates/base/package.json +14 -0
  14. package/templates/base/public/vite.svg +1 -0
  15. package/templates/base/src/App.tsx +9 -0
  16. package/templates/base/src/api/api.ts +29 -0
  17. package/templates/base/src/api/http.ts +27 -0
  18. package/templates/base/src/api/index.ts +2 -0
  19. package/templates/base/src/assets/react.svg +1 -0
  20. package/templates/base/src/layout/AuthLayout.tsx +0 -0
  21. package/templates/base/src/layout/MainLayout.tsx +0 -0
  22. package/templates/base/src/layout/index.ts +2 -0
  23. package/templates/base/src/main.tsx +9 -0
  24. package/templates/base/src/modules/auth/components/AuthForm.tsx +5 -0
  25. package/templates/base/src/modules/auth/hooks/useAuth.ts +55 -0
  26. package/templates/base/src/modules/auth/pages/ForgotPassword.tsx +5 -0
  27. package/templates/base/src/modules/auth/pages/Login.tsx +5 -0
  28. package/templates/base/src/modules/auth/pages/Register.tsx +5 -0
  29. package/templates/base/src/modules/auth/pages/index.ts +3 -0
  30. package/templates/base/src/modules/auth/services/auth.service.ts +17 -0
  31. package/templates/base/src/modules/auth/services/endpoint.ts +10 -0
  32. package/templates/base/src/modules/auth/services/index.ts +3 -0
  33. package/templates/base/src/modules/auth/services/oauth.service.ts +1 -0
  34. package/templates/base/src/modules/auth/services/password.service.ts +1 -0
  35. package/templates/base/src/modules/auth/types/auth.types.ts +3 -0
  36. package/templates/base/src/modules/auth/types/index.ts +3 -0
  37. package/templates/base/src/modules/auth/types/oauth.types.ts +1 -0
  38. package/templates/base/src/modules/auth/types/password.types.ts +1 -0
  39. package/templates/base/src/modules/common/pages/NotFound.tsx +14 -0
  40. package/templates/base/src/modules/common/pages/index.ts +1 -0
  41. package/templates/base/src/utils/api-error-handler.ts +25 -0
  42. package/templates/base/tsconfig.app.json +32 -0
  43. package/templates/base/tsconfig.json +13 -0
  44. package/templates/base/tsconfig.node.json +26 -0
  45. package/templates/base/vite-env.d.ts +1 -0
  46. package/templates/base/vite.config.ts +18 -0
  47. package/templates/hooks/index.ts +0 -0
  48. package/templates/hooks/useDebounce.ts +1 -0
  49. package/templates/hooks/useTheme.ts +1 -0
  50. package/templates/hooks/useThrottle.ts +1 -0
  51. package/templates/hooks/useWebStorage.ts +1 -0
  52. package/templates/router/react-router/src/components/ProtectedRoute.tsx +20 -0
  53. package/templates/router/react-router/src/components/PublicRoute.tsx +20 -0
  54. package/templates/router/react-router/src/modules/auth/routes/index.tsx +21 -0
  55. package/templates/router/react-router/src/routes/AppRoutes.tsx +18 -0
  56. package/templates/router/react-router/src/routes/index.ts +3 -0
  57. package/templates/router/tanstack-router/src/providers/TanstackRouterProvider.tsx +14 -0
  58. package/templates/router/tanstack-router/src/routes/__root.tsx +13 -0
  59. package/templates/router/tanstack-router/src/routes/_protected/index.tsx +10 -0
  60. package/templates/router/tanstack-router/src/routes/_protected.tsx +18 -0
  61. package/templates/router/tanstack-router/src/routes/_public/forgot-password.tsx +6 -0
  62. package/templates/router/tanstack-router/src/routes/_public/login.tsx +6 -0
  63. package/templates/router/tanstack-router/src/routes/_public/register.tsx +6 -0
  64. package/templates/router/tanstack-router/src/routes/_public.tsx +13 -0
  65. package/templates/state/react-query/src/hook/useAuth.ts +43 -0
  66. package/templates/state/react-query/src/provider/ReactQueryProvider.tsx +22 -0
  67. package/templates/state/react-query/src/provider/index.ts +1 -0
  68. package/templates/state/zustand/src/auth/useAuthStore.ts +29 -0
  69. package/templates/state/zustand/src/index.ts +2 -0
  70. package/templates/state/zustand/src/theme/useThemeStore.ts +27 -0
  71. package/templates/styles/css/src/404.css +101 -0
  72. package/templates/styles/css/src/main.css +70 -0
  73. package/templates/styles/css/src/variables.css +15 -0
  74. package/templates/styles/scss/src/404.scss +103 -0
  75. package/templates/styles/scss/src/_index.scss +2 -0
  76. package/templates/styles/scss/src/_mixins.scss +27 -0
  77. package/templates/styles/scss/src/_variables.scss +28 -0
  78. package/templates/styles/scss/src/main.scss +20 -0
  79. package/templates/styles/tailwind/config/_vite.config.ts +14 -0
  80. package/templates/styles/tailwind/src/404.css +98 -0
  81. package/templates/styles/tailwind/src/main.css +123 -0
  82. package/templates/ui/shadcn/src/components/ui/button.tsx +62 -0
package/README.md ADDED
@@ -0,0 +1,150 @@
1
+ # @aditokmo/react-setup-cli 🚀
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@aditokmo/create-react-project?color=blue)](https://www.npmjs.com/package/@aditokmo/create-react-project)
4
+
5
+ A React CLI built on top of Vite that helps you build and structure projects in seconds. It eliminates manual setup by configuring your favorite tools into a **clean, modular architecture** automatically.
6
+
7
+ **Note:** This package is a CLI tool. Do not install it with `npm i`. Instead check `Quick Start` down below.
8
+
9
+ <br>
10
+
11
+ ## Quick Start
12
+
13
+ Run the following command in your terminal to start CLI
14
+
15
+ ```bash
16
+ # Using PNPM
17
+ pnpm create @aditokmo/react-project
18
+
19
+ # Using NPM
20
+ npm create @aditokmo/react-project
21
+
22
+ # Using Yarn
23
+ yarn create @aditokmo/react-project
24
+ ```
25
+
26
+ <br>
27
+
28
+ ## How it saves your time
29
+
30
+ - **Minimal Installation:** No more manual `npm install` for 10 different packages. The CLI detects your package manager and handles everything.
31
+ - **Architecture:** Instead of losing time on folder structure, you get a structure that is ready for large-scale production.
32
+ - **Smart Boilerplate Injection:** It doesn't just create files, it also wires them following best practices from documentation.
33
+
34
+ <br>
35
+
36
+ ## Use Cases when this CLI is useful:
37
+
38
+ - **Starting a Professional Project:** When you need a project that follows "Clean Architecture" and industry standards from the very first commit.
39
+ - **Focusing on a Specific Feature:** When you want to test a new library or a specific piece of logic, but you need a proper environment to do it. This CLI lets you focus on what you are testing instead of wasting time on a setup.
40
+ - **Prototyping & MVP:** When you have a startup idea and want to to build actual features right away without sacrificing code quality.
41
+ - **Hackathons:** When every second counts. You can get all your configuration and setup ready before the competition start.
42
+
43
+ <br>
44
+
45
+ ## About Arhitecture
46
+
47
+ ### Folder Structure
48
+
49
+ - **Feature-Based**: This architecture uses a modular approach to help you build large-scale projects. Instead of mixing all components and hooks into global folders, everything is grouped by domain, such as Auth or Dashboard. This method makes it much easier to navigate the codebase and ensures your project remains maintainable as it grows.
50
+
51
+ - **Pages**: Comming soon
52
+
53
+ ### Client State Management
54
+
55
+ - **Zustand** is currently the only option in CLI for global state management. It is the most popular and easiest-to-use library today. I believe Redux is overkill for most modern projects. Most "global state" is now handled as server-state by TanStack Query. Global state should be reserved for things like authentication and UI stuff, and Zustand handles this perfectly with zero boilerplate.
56
+
57
+ ### Server State Management
58
+
59
+ - **TanStack Query** is integrated to handle server-state management. It is optional, but if selected, the CLI automatically wires up the necessary providers and configurations so you can start fetching data immediately. If you select to not use react query, you still get a traditional boilerplate for manual data handling.
60
+
61
+ ### Axios
62
+
63
+ The CLI generates a pre-configured Axios client that serves as your central API bridge. It includes ready-to-use interceptors for handling authorization tokens and global error responses, saving you from writing the same repetitive setup every time. Axios is set by default beacause it is better choice then fetch, Axios is more user-friendly, has better error handling out of the box, and is overall a safer and more robust choice for production apps.
64
+
65
+ ### Styling
66
+
67
+ You can choose between **CSS**, **SCSS**, or **TailwindCSS**. While I personally recommend Tailwind for modern and faster development, the CLI ensures that regardless of your choice, the project is configured with a global styles directory and a consistent entry point. If you select TailwindCSS you will also have option to use Shadcn/UI, and with that you will have option to choose components that you want to install instead of doing it manually.
68
+
69
+ ### Routing
70
+
71
+ - **React Router** is the industry standard that most developers are familiar with.
72
+
73
+ - **TanStack Router** is included for those who want a fully type-safe routing experience with built-in data loading capabilities.
74
+
75
+ Whichever you choose, the CLI doesn't just install the library it will generate a `routes/` directory system to help you easily separate your public pages from protected pages.
76
+
77
+ ### Package Manager Detection
78
+
79
+ To make the workflow even smoother, the tool has an automatic package manager detector. It identifies whether you are using **npm**, **pnpm**, or **yarn** based on the command you used to execute the CLI, and it handles all installations using your preferred package manager to ensure consistency and avoid conflicts.
80
+
81
+ <br>
82
+
83
+ ## Folder Structure
84
+
85
+ **Note**: This is the complete folder structure. The actual folders generated will depend on the libraries and options you select during the setup process.
86
+
87
+ ### Feature-Based (modules)
88
+
89
+ ```text
90
+ src/
91
+ ├── api/ # Global API client & Axios config
92
+ ├── components/ # Shared UI components
93
+ ├── hooks/ # Global reusable custom hooks
94
+ ├── providers/ # Providers (React Query, Tanstack Router)
95
+ ├── routes/ # Route definitions
96
+ ├── styles/ # Styles (Tailwind, CSS, SCSS)
97
+ ├── layout/ # Layout to split protected and unprotected routes
98
+ │ ├── MainLayout.tsx
99
+ │ ├── AuthLayout.tsx
100
+ ├── modules/ # Feature-based modules (The core of your app)
101
+ | ├── common/ # Shared components & pages (404 Page, Navbar, Sidebar)
102
+ │ │ ├── pages/
103
+ │ │ └── components/
104
+ │ └── auth/ # Example: Auth module
105
+ │ ├── components/
106
+ │ ├── hooks/
107
+ │ ├── pages/
108
+ │ ├── services/
109
+ │ └── types/
110
+ ├── store/ # Global State Managemenet (Zustand)
111
+ │ ├── useAuthStore.ts
112
+ │ └── useThemeStore.ts
113
+ └── utils/ # Helper functions
114
+ ```
115
+
116
+ <br>
117
+
118
+ ## Features
119
+
120
+ | Category | Options |
121
+ | :------------------- | :-------------------------------------- |
122
+ | **Folder Structure** | Feature-based |
123
+ | **Modules** | Common, Auth |
124
+ | **Routing** | React Router, TanStack Router |
125
+ | **Data Fetching** | TanStack Query (React Query) & Axios |
126
+ | **State Management** | Zustand |
127
+ | **Form** | React Hook Form, TanStack Form |
128
+ | **Schema** | Zod, Yup |
129
+ | **Styling** | CSS, SCSS, Tailwind CSS |
130
+ | **UI Components** | Shadcn |
131
+ | **Icons** | React Icons, Font Awesome |
132
+ | **Toast** | React Toastify, React Hot Toast, Sonner |
133
+ | **Custom Hooks** | |
134
+ | **Helpers** | |
135
+
136
+ <br>
137
+
138
+ ## Future of CLI
139
+
140
+
141
+ - Options to choose between React, Next.js and TanStack Start
142
+ - Testing tools
143
+ - i18next pre-setup.
144
+ - Supabase & Firebase integration templates
145
+ - Global custom hooks & helper functions
146
+ - Pre-commit linters
147
+ - Github Action workflow
148
+ - TanStack Table (if your app has some kind of tables)
149
+
150
+ <br>
package/dist/index.js ADDED
@@ -0,0 +1,129 @@
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, detectPackageManager, patchPackageJsonFile, patchFileContent } from './utils.js';
7
+ import { collectDependencies } from './installers.js';
8
+ import { fileURLToPath } from 'url';
9
+ import { FONT_QUERIES } from './mapper.js';
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+ const baseDeps = ['react', 'react-dom'];
13
+ const baseDevDeps = [
14
+ 'vite',
15
+ 'typescript',
16
+ '@types/node',
17
+ '@types/react',
18
+ '@types/react-dom',
19
+ '@vitejs/plugin-react-swc',
20
+ 'eslint',
21
+ '@eslint/js',
22
+ 'eslint-plugin-react-hooks',
23
+ 'eslint-plugin-react-refresh',
24
+ 'globals',
25
+ 'typescript-eslint'
26
+ ];
27
+ async function main() {
28
+ console.log('⚛️ Welcome to React CLI Setup by github/aditokmo');
29
+ let projectDir = '';
30
+ const originalDirectory = process.cwd();
31
+ try {
32
+ const answers = await askQuestions();
33
+ const packageManager = detectPackageManager();
34
+ const installAction = packageManager === 'yarn' ? 'add' : 'install';
35
+ projectDir = path.join(process.cwd(), answers.projectName);
36
+ const templateRoot = path.join(__dirname, '../templates');
37
+ const appFilePath = path.join(projectDir, 'src', 'App.tsx');
38
+ const indexHTMLPath = path.join(projectDir, 'index.html');
39
+ const notFoundPath = path.join(projectDir, 'src/modules/common/pages/NotFound.tsx');
40
+ const viteConfigPath = path.join(projectDir, 'vite.config.ts');
41
+ fs.ensureDirSync(projectDir);
42
+ copyTemplate(path.join(templateRoot, 'base'), projectDir);
43
+ patchPackageJsonFile(path.join(projectDir, 'package.json'), answers.projectName);
44
+ // Styles
45
+ if (answers.style === 'tailwind') {
46
+ copyTemplate(path.join(templateRoot, 'styles', 'tailwind', 'src'), path.join(projectDir, 'src/styles'));
47
+ patchViteConfig(viteConfigPath, false, 'import tailwindcss from "@tailwindcss/vite"', 'tailwindcss()');
48
+ }
49
+ if (answers.style === 'css') {
50
+ copyTemplate(path.join(templateRoot, 'styles', 'css', 'src'), path.join(projectDir, 'src/styles'));
51
+ }
52
+ if (answers.style === 'scss') {
53
+ copyTemplate(path.join(templateRoot, 'styles', 'scss', 'src'), path.join(projectDir, 'src/styles'));
54
+ patchFileContent(notFoundPath, "import '@/styles/404.css'", "import '@/styles/404.scss'");
55
+ patchFileContent(indexHTMLPath, '<link rel="stylesheet" href="./src/styles/main.css" />', '<link rel="stylesheet" href="./src/styles/main.scss" />');
56
+ }
57
+ // Fonts
58
+ if (answers?.fonts && answers.fonts.length > 0) {
59
+ const fontString = answers.fonts
60
+ .map(name => FONT_QUERIES[name])
61
+ .join('&');
62
+ const url = `https://fonts.googleapis.com/css2?${fontString}&display=swap`;
63
+ const content = `
64
+ <link rel="preconnect" href="https://fonts.googleapis.com">
65
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
66
+ <link href="${url}" rel="stylesheet">`;
67
+ patchFileContent(indexHTMLPath, '<!-- [HEAD_LINK_IMPORT] -->', content);
68
+ }
69
+ else {
70
+ patchFileContent(indexHTMLPath, '<!-- [HEAD_LINK_IMPORT] -->', '');
71
+ }
72
+ // State Management
73
+ if (answers.reactQuery) {
74
+ copyTemplate(path.join(templateRoot, 'state', 'react-query', 'src', 'provider'), path.join(projectDir, 'src/providers'));
75
+ copyTemplate(path.join(templateRoot, 'state', 'react-query', 'src', 'hook'), path.join(projectDir, 'src/modules/auth/hooks'));
76
+ patchAppFile(appFilePath, "import { ReactQueryProvider } from './providers'", "<ReactQueryProvider>", "</ReactQueryProvider>");
77
+ }
78
+ if (answers.globalState === 'zustand') {
79
+ copyTemplate(path.join(templateRoot, 'state', 'zustand', 'src'), path.join(projectDir, 'src/store'));
80
+ }
81
+ // Router
82
+ if (answers.router === 'react-router') {
83
+ copyTemplate(path.join(templateRoot, 'router', 'react-router', 'src', 'routes'), path.join(projectDir, 'src/routes'));
84
+ copyTemplate(path.join(templateRoot, 'router', 'react-router', 'src', 'components'), path.join(projectDir, 'src/components'));
85
+ copyTemplate(path.join(templateRoot, 'router', 'react-router', 'src', 'modules', 'auth', 'routes'), path.join(projectDir, 'src/modules/auth/routes'));
86
+ patchAppFile(appFilePath, "import { BrowserRouter } from 'react-router'\nimport { AppRoutes } from './routes'", "<BrowserRouter>\n <AppRoutes />", "</BrowserRouter>");
87
+ }
88
+ else if (answers.router === 'tanstack-router') {
89
+ copyTemplate(path.join(templateRoot, 'router', 'tanstack-router', 'src', 'routes'), path.join(projectDir, 'src/routes'));
90
+ copyTemplate(path.join(templateRoot, 'router', 'tanstack-router', 'src', 'providers'), path.join(projectDir, 'src/providers'));
91
+ patchAppFile(appFilePath, "import { TanStackRouterProvider } from './providers/TanstackRouterProvider'", "<TanStackRouterProvider />", "");
92
+ patchViteConfig(viteConfigPath, true, "import { tanstackRouter } from '@tanstack/router-plugin/vite'", "tanstackRouter({ target: 'react', autoCodeSplitting: true })");
93
+ }
94
+ finalizeAppFile(appFilePath);
95
+ finalizeViteConfig(viteConfigPath);
96
+ const { dependency, devDependency, cmd } = collectDependencies(answers, packageManager);
97
+ const allDeps = [...baseDeps, ...dependency];
98
+ const allDevDeps = [...baseDevDeps, ...devDependency];
99
+ process.chdir(projectDir);
100
+ console.log(`📦 Initializing ${packageManager} project...`);
101
+ execSync(`${packageManager} install`, { stdio: 'inherit' });
102
+ if (allDeps.length) {
103
+ console.log('📦 Installing dependencies...');
104
+ execSync(`${packageManager} ${installAction} ${allDeps.join(' ')}`, { stdio: 'inherit' });
105
+ }
106
+ if (allDevDeps.length) {
107
+ console.log('📦 Installing dev dependencies...');
108
+ execSync(`${packageManager} ${installAction} -D ${allDevDeps.join(' ')}`, { stdio: 'inherit' });
109
+ }
110
+ // Extra cmds like shadcn
111
+ for (const command of cmd) {
112
+ console.log(`⚙️ Running: ${command}`);
113
+ execSync(command, { stdio: 'inherit' });
114
+ }
115
+ console.log('âś… Project setup completed');
116
+ console.log(`👉 cd ${answers.projectName} && ${packageManager} run dev`);
117
+ }
118
+ catch (error) {
119
+ console.error('\n❌ Error while creating a project:');
120
+ console.error(`👉 ${error.message}`);
121
+ process.chdir(originalDirectory);
122
+ if (projectDir && fs.existsSync(projectDir)) {
123
+ console.log('đź§ą Cleaning... Deleting failed project installation.');
124
+ fs.removeSync(projectDir);
125
+ }
126
+ process.exit(1);
127
+ }
128
+ }
129
+ main();
@@ -0,0 +1,30 @@
1
+ import { installers } from './mapper.js';
2
+ import { axiosInstaller } from './packages.js';
3
+ export function collectDependencies(answers, packageManager) {
4
+ const dependency = new Set();
5
+ const devDependency = new Set();
6
+ const cmd = [];
7
+ const dlx = packageManager === 'npm' ? 'npx --yes' : `${packageManager} dlx`;
8
+ // Default base packages
9
+ axiosInstaller.dependency?.forEach(d => dependency.add(d));
10
+ // Packages from answers
11
+ Object.entries(answers).forEach(([key, value]) => {
12
+ if (Array.isArray(value))
13
+ return;
14
+ const installer = installers[value === true ? key : value];
15
+ installer?.dependency?.forEach(d => dependency.add(d));
16
+ installer?.devDependency?.forEach(d => devDependency.add(d));
17
+ });
18
+ if (answers.shadcn && answers.style === 'tailwind') {
19
+ cmd.push(`${dlx} shadcn@latest init -d`);
20
+ if (answers.shadcnComponents && answers.shadcnComponents.length > 0) {
21
+ const componentsStr = answers.shadcnComponents.join(' ');
22
+ cmd.push(`${dlx} shadcn@latest add ${componentsStr} -y -o`);
23
+ }
24
+ }
25
+ return {
26
+ dependency: Array.from(dependency),
27
+ devDependency: Array.from(devDependency),
28
+ cmd
29
+ };
30
+ }
package/dist/mapper.js ADDED
@@ -0,0 +1,28 @@
1
+ import { fontAwesomeIconsInstaller, phosphorIconsInstaller, reactFormHookInstaller, reactHotToastInstaller, reactIconsInstaller, reactQueryInstaller, reactRouterInstaller, reactToastifyInstaller, scssInstaller, 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
+ 'scss': scssInstaller,
9
+ 'react-icons': reactIconsInstaller,
10
+ 'font-awesome': fontAwesomeIconsInstaller,
11
+ 'phosphor-icons': phosphorIconsInstaller,
12
+ 'react-hot-toast': reactHotToastInstaller,
13
+ 'react-toastify': reactToastifyInstaller,
14
+ 'sonner': sonnerInstaller,
15
+ 'react-hook-form': reactFormHookInstaller,
16
+ 'tanstack-form': tanstackFormInstaller,
17
+ 'zod': zodInstaller,
18
+ 'yup': yupInstaller,
19
+ };
20
+ export const FONT_QUERIES = {
21
+ "geist": "family=Geist:wght@100..900",
22
+ "inter": "family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900",
23
+ "lato": "family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900",
24
+ "montserrat": "family=Montserrat:ital,wght@0,100..900;1,100..900",
25
+ "open-sans": "family=Open+Sans:ital,wght@0,300..800;1,300..800",
26
+ "poppins": "family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900",
27
+ "roboto": "family=Roboto:ital,wght@0,100..900;1,100..900"
28
+ };
@@ -0,0 +1,62 @@
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
+ export const scssInstaller = {
15
+ devDependency: ['sass']
16
+ };
17
+ // Icons
18
+ export const reactIconsInstaller = {
19
+ dependency: ['react-icons'],
20
+ };
21
+ export const fontAwesomeIconsInstaller = {
22
+ dependency: ['@fortawesome/fontawesome-svg-core', '@fortawesome/react-fontawesome', '@fortawesome/free-solid-svg-icons', '@fortawesome/free-regular-svg-icons', '@fortawesome/free-brands-svg-icons'],
23
+ };
24
+ export const phosphorIconsInstaller = {
25
+ dependency: ['phosphor-react'],
26
+ };
27
+ // Toast
28
+ export const reactHotToastInstaller = {
29
+ dependency: ['react-hot-toast'],
30
+ };
31
+ export const reactToastifyInstaller = {
32
+ dependency: ['react-toastify'],
33
+ };
34
+ export const sonnerInstaller = {
35
+ dependency: ['sonner'],
36
+ };
37
+ // Routers
38
+ export const reactRouterInstaller = {
39
+ dependency: ['react-router', 'react-router-dom'],
40
+ };
41
+ export const tanstackRouterInstaller = {
42
+ dependency: ['@tanstack/react-router'],
43
+ devDependency: ['@tanstack/react-router-devtools', '@tanstack/router-plugin'],
44
+ };
45
+ // Forms
46
+ export const reactFormHookInstaller = {
47
+ dependency: ['react-hook-form'],
48
+ };
49
+ export const tanstackFormInstaller = {
50
+ dependency: ['@tanstack/react-form'],
51
+ };
52
+ // Schemas
53
+ export const zodInstaller = {
54
+ dependency: ['zod', '@hookform/resolvers'],
55
+ };
56
+ export const yupInstaller = {
57
+ dependency: ['yup', '@hookform/resolvers'],
58
+ };
59
+ // Base installation
60
+ export const axiosInstaller = {
61
+ dependency: ['axios'],
62
+ };
@@ -0,0 +1,123 @@
1
+ import { group, text, select, confirm, isCancel, cancel, multiselect } 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
+ }),
8
+ style: () => select({
9
+ message: 'Choose styling:',
10
+ options: [
11
+ { value: 'tailwind', label: 'Tailwind' },
12
+ { value: 'css', label: 'CSS' },
13
+ { value: 'scss', label: 'SCSS' },
14
+ ],
15
+ }),
16
+ shadcn: ({ results }) => {
17
+ if (results.style !== 'tailwind')
18
+ return Promise.resolve(false);
19
+ return confirm({ message: 'Include Shadcn UI?' });
20
+ },
21
+ shadcnComponents: ({ results }) => {
22
+ if (!results.shadcn)
23
+ return Promise.resolve([]);
24
+ return multiselect({
25
+ message: 'Use space to select shadcn/ui components to install:',
26
+ options: [
27
+ { value: 'button', label: 'Button' },
28
+ { value: 'input', label: 'Input' },
29
+ { value: 'card', label: 'Card' },
30
+ { value: 'dialog', label: 'Dialog' },
31
+ { value: 'sheet', label: 'Sheet' },
32
+ { value: 'dropdown-menu', label: 'Dropdown Menu' },
33
+ { value: 'table', label: 'Table' },
34
+ { value: 'checkbox', label: 'Checkbox' },
35
+ { value: 'avatar', label: 'Avatar' },
36
+ { value: 'badge', label: 'Badge' },
37
+ ],
38
+ required: false,
39
+ });
40
+ },
41
+ fonts: () => multiselect({
42
+ message: 'Use space to select your fonts',
43
+ options: [
44
+ { value: 'geist', label: 'Geist' },
45
+ { value: 'inter', label: 'Inter' },
46
+ { value: 'lato', label: 'Lato' },
47
+ { value: 'montserrat', label: 'Montserrat' },
48
+ { value: 'open-sans', label: 'Open Sans' },
49
+ { value: 'poppins', label: 'Poppins' },
50
+ { value: 'roboto', label: 'Roboto' }
51
+ ],
52
+ required: false
53
+ }),
54
+ icons: () => select({
55
+ message: 'Choose icon library:',
56
+ options: [
57
+ { value: 'react-icons', label: 'React Icons' },
58
+ { value: 'font-awesome', label: 'Font Awesome' },
59
+ { value: 'phosphor-icons', label: 'Phosphor Icons' }
60
+ ]
61
+ }),
62
+ toast: () => select({
63
+ message: 'Choose toast library:',
64
+ options: [
65
+ { value: 'react-hot-toast', label: 'React Hot Toast' },
66
+ { value: 'react-toastify', label: 'React Toastify' },
67
+ { value: 'sonner', label: 'Sonner' },
68
+ ]
69
+ }),
70
+ router: () => select({
71
+ message: 'Choose router:',
72
+ options: [
73
+ { value: 'react-router', label: 'React Router' },
74
+ { value: 'tanstack-router', label: 'Tanstack Router' },
75
+ ],
76
+ }),
77
+ form: () => select({
78
+ message: 'Choose form library:',
79
+ options: [
80
+ { value: 'react-hook-form', label: 'React Hook Form' },
81
+ { value: 'tanstack-form', label: 'TanStack Form' },
82
+ ],
83
+ }),
84
+ schema: () => select({
85
+ message: 'Choose schema library:',
86
+ options: [
87
+ { value: 'zod', label: 'Zod' },
88
+ { value: 'yup', label: 'Yup' },
89
+ ]
90
+ }),
91
+ globalState: () => select({
92
+ message: 'Choose global state management library:',
93
+ options: [
94
+ { value: 'zustand', label: 'Zustand' },
95
+ ]
96
+ }),
97
+ reactQuery: () => confirm({ message: 'Include React Query?' }),
98
+ }, {
99
+ onCancel: () => {
100
+ cancel('Operation cancelled.');
101
+ process.exit(0);
102
+ },
103
+ });
104
+ if (isCancel(results.projectName) ||
105
+ isCancel(results.style) ||
106
+ isCancel(results.router) ||
107
+ isCancel(results.shadcn) ||
108
+ isCancel(results.shadcnComponents) ||
109
+ isCancel(results.icons) ||
110
+ isCancel(results.toast) ||
111
+ isCancel(results.reactQuery) ||
112
+ isCancel(results.fonts)) {
113
+ cancel('Setup cancelled.');
114
+ process.exit(0);
115
+ }
116
+ return {
117
+ ...results,
118
+ projectName: results.projectName || 'my-app',
119
+ shadcn: results.shadcn,
120
+ shadcnComponents: (results.shadcnComponents ?? []),
121
+ fonts: (results.fonts ?? [])
122
+ };
123
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/utils.js ADDED
@@ -0,0 +1,101 @@
1
+ import fs from 'fs-extra';
2
+ export function copyTemplate(src, path) {
3
+ if (!fs.existsSync(src)) {
4
+ console.warn(`⚠️ Template folder ${src} doesn't exist`);
5
+ return;
6
+ }
7
+ fs.copySync(src, path, { overwrite: true });
8
+ }
9
+ export function patchFileContent(filePath, searchValue, replaceValue) {
10
+ if (!fs.existsSync(filePath))
11
+ return;
12
+ const content = fs.readFileSync(filePath, 'utf-8');
13
+ const newContent = content.replace(searchValue, replaceValue);
14
+ fs.writeFileSync(filePath, newContent);
15
+ }
16
+ export function patchAppFile(filePath, importLine, openTag, closeTag) {
17
+ if (!fs.existsSync(filePath))
18
+ return;
19
+ let content = fs.readFileSync(filePath, 'utf-8');
20
+ if (importLine) {
21
+ content = content.replace('// [IMPORTS]', `${importLine}\n// [IMPORTS]`);
22
+ }
23
+ if (openTag) {
24
+ if (content.includes('// [PROVIDERS]')) {
25
+ const initialContent = closeTag
26
+ ? `${openTag}\n // [PROVIDERS]\n ${closeTag}`
27
+ : openTag;
28
+ content = content.replace('// [PROVIDERS]', initialContent);
29
+ }
30
+ else {
31
+ content = content.replace(/return \(([\s\S]*?)\)/, (match, inner) => {
32
+ return `return (\n ${openTag}${inner.trim()}\n ${closeTag}\n )`;
33
+ });
34
+ }
35
+ }
36
+ fs.writeFileSync(filePath, content);
37
+ }
38
+ export function patchPackageJsonFile(filePath, projectName) {
39
+ if (!fs.existsSync(filePath))
40
+ return;
41
+ try {
42
+ const pkg = fs.readJsonSync(filePath);
43
+ pkg.name = projectName;
44
+ fs.writeJsonSync(filePath, pkg, { spaces: 2 });
45
+ }
46
+ catch (error) {
47
+ console.error('⚠️ Could not update package.json name');
48
+ }
49
+ }
50
+ export function patchViteConfig(filePath, beforeReactPlugin, importLine, pluginLine) {
51
+ if (!fs.existsSync(filePath))
52
+ return;
53
+ let content = fs.readFileSync(filePath, 'utf-8');
54
+ if (importLine && !content.includes(importLine)) {
55
+ content = content.replace('// [IMPORTS]', `${importLine}\n// [IMPORTS]`);
56
+ }
57
+ if (beforeReactPlugin) {
58
+ if (pluginLine && !content.includes(pluginLine)) {
59
+ content = content.replace('// [BEFORE_REACT_PLUGINS]', `${pluginLine},\n // [BEFORE_REACT_PLUGINS]`);
60
+ }
61
+ }
62
+ else {
63
+ if (pluginLine && !content.includes(pluginLine)) {
64
+ content = content.replace('// [AFTER_REACT_PLUGINS]', `${pluginLine},\n // [AFTER_REACT_PLUGINS]`);
65
+ }
66
+ }
67
+ fs.writeFileSync(filePath, content);
68
+ }
69
+ export function finalizeViteConfig(filePath) {
70
+ if (!fs.existsSync(filePath))
71
+ return;
72
+ let content = fs.readFileSync(filePath, 'utf-8');
73
+ content = content
74
+ // Briše marker i sav prazan prostor (redove) oko njega
75
+ .replace(/\s*\/\/ \[IMPORTS\]\s*/g, '\n')
76
+ .replace(/\s*\/\/ \[AFTER_REACT_PLUGINS\]\s*/g, '\n')
77
+ .replace(/\s*\/\/ \[BEFORE_REACT_PLUGINS\]\s*/g, '\n')
78
+ // Sređuje višestruke prazne redove u jedan
79
+ .replace(/\n{3,}/g, '\n\n')
80
+ .trim();
81
+ fs.writeFileSync(filePath, content + '\n');
82
+ }
83
+ export function finalizeAppFile(filePath) {
84
+ if (!fs.existsSync(filePath))
85
+ return;
86
+ let content = fs.readFileSync(filePath, 'utf-8');
87
+ content = content
88
+ .replace(/\s*\/\/ \[IMPORTS\]\s*/g, '\n')
89
+ .replace(/\s*\/\/ \[PROVIDERS\]\s*/g, '\n')
90
+ .replace(/\n{3,}/g, '\n\n')
91
+ .trim();
92
+ fs.writeFileSync(filePath, content + '\n');
93
+ }
94
+ export function detectPackageManager() {
95
+ const userAgent = process.env.npm_config_user_agent || '';
96
+ if (userAgent.includes('pnpm'))
97
+ return 'pnpm';
98
+ if (userAgent.includes('yarn'))
99
+ return 'yarn';
100
+ return 'npm';
101
+ }