create-rvp 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.
- package/LICENSE +21 -0
- package/README.md +158 -0
- package/bin/index.js +2 -0
- package/dist/cli.js +459 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mostafa Khaled
|
|
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,158 @@
|
|
|
1
|
+
# crvp CLI
|
|
2
|
+
|
|
3
|
+
Interactive CLI for creating modern React + Vite projects with optional Tailwind CSS, Redux, React Query, and more.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/crvp)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://nodejs.org/)
|
|
8
|
+
|
|
9
|
+
## 🚀 Features
|
|
10
|
+
|
|
11
|
+
- **Interactive Prompts** - Guided project setup with beautiful CLI interface
|
|
12
|
+
- **Vite-powered** - Fast, modern build tool with HMR
|
|
13
|
+
- **TypeScript Support** - Optional TypeScript scaffolding
|
|
14
|
+
- **Modern Stack** - React 19, React Compiler, Tailwind CSS
|
|
15
|
+
- **State Management** - Optional Redux Toolkit integration
|
|
16
|
+
- **Data Fetching** - React Query (TanStack Query) + Axios
|
|
17
|
+
- **Form Handling** - React Hook Form with Zod validation
|
|
18
|
+
- **Routing** - React Router setup
|
|
19
|
+
- **Linting** - ESLint + Prettier configuration
|
|
20
|
+
- **Flexible Structure** - Simple or scalable folder architecture
|
|
21
|
+
|
|
22
|
+
## 📦 Installation
|
|
23
|
+
|
|
24
|
+
### Global Installation (Recommended)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install -g create-rvp
|
|
28
|
+
# or
|
|
29
|
+
pnpm add -g create-rvp
|
|
30
|
+
# or
|
|
31
|
+
yarn global add create-rvp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Using npx (No Installation)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx create-rvp [project-name]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 🛠️ Usage
|
|
41
|
+
|
|
42
|
+
### Create a new project
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# With interactive prompts
|
|
46
|
+
create-rvp
|
|
47
|
+
|
|
48
|
+
# With project name argument
|
|
49
|
+
create-rvp my-awesome-app
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Options
|
|
53
|
+
|
|
54
|
+
The CLI will guide you through selecting:
|
|
55
|
+
|
|
56
|
+
| Option | Description |
|
|
57
|
+
|--------|-------------|
|
|
58
|
+
| Package Manager | pnpm (default), npm, yarn, or bun |
|
|
59
|
+
| TypeScript | Enable TypeScript support |
|
|
60
|
+
| React Compiler | Enable React 19 compiler optimizations |
|
|
61
|
+
| Tailwind CSS | Add Tailwind CSS for styling |
|
|
62
|
+
| React Router | Include client-side routing |
|
|
63
|
+
| React Query + Axios | Add TanStack Query and Axios for data fetching |
|
|
64
|
+
| Redux Toolkit | Include Redux for global state management |
|
|
65
|
+
| React Hook Form + Zod | Form handling with schema validation |
|
|
66
|
+
| ESLint + Prettier | Code linting and formatting setup |
|
|
67
|
+
| Folder Structure | Simple (flat) or Scalable (feature-based) |
|
|
68
|
+
|
|
69
|
+
## 📁 Project Structure
|
|
70
|
+
|
|
71
|
+
### Simple Structure
|
|
72
|
+
```
|
|
73
|
+
src/
|
|
74
|
+
├── components/
|
|
75
|
+
├── pages/
|
|
76
|
+
├── hooks/
|
|
77
|
+
├── utils/
|
|
78
|
+
├── styles/
|
|
79
|
+
└── App.tsx
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Scalable Structure
|
|
83
|
+
```
|
|
84
|
+
src/
|
|
85
|
+
├── features/
|
|
86
|
+
│ └── feature-name/
|
|
87
|
+
│ ├── components/
|
|
88
|
+
│ ├── hooks/
|
|
89
|
+
│ └── api/
|
|
90
|
+
├── shared/
|
|
91
|
+
│ ├── components/
|
|
92
|
+
│ ├── hooks/
|
|
93
|
+
│ └── utils/
|
|
94
|
+
└── App.tsx
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 🧰 Technologies
|
|
98
|
+
|
|
99
|
+
- [Vite](https://vitejs.dev/) - Next Generation Frontend Tooling
|
|
100
|
+
- [React](https://react.dev/) - A JavaScript library for building user interfaces
|
|
101
|
+
- [TypeScript](https://www.typescriptlang.org/) - Typed JavaScript
|
|
102
|
+
- [Tailwind CSS](https://tailwindcss.com/) - Utility-first CSS framework
|
|
103
|
+
- [React Query](https://tanstack.com/query) - Powerful asynchronous state management
|
|
104
|
+
- [Redux Toolkit](https://redux-toolkit.js.org/) - State management made simple
|
|
105
|
+
- [React Hook Form](https://react-hook-form.com/) - Performant forms
|
|
106
|
+
- [Zod](https://zod.dev/) - TypeScript-first schema validation
|
|
107
|
+
- [React Router](https://reactrouter.com/) - Declarative routing
|
|
108
|
+
|
|
109
|
+
## 🧑💻 Development
|
|
110
|
+
|
|
111
|
+
### Prerequisites
|
|
112
|
+
|
|
113
|
+
- Node.js >= 18
|
|
114
|
+
- pnpm (recommended), npm, or yarn
|
|
115
|
+
|
|
116
|
+
### Setup
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Clone the repository
|
|
120
|
+
git clone https://github.com/M0stafa-Khaled/create-rvp.git
|
|
121
|
+
cd create-rvp
|
|
122
|
+
|
|
123
|
+
# Install dependencies
|
|
124
|
+
pnpm install
|
|
125
|
+
|
|
126
|
+
# Run in development mode
|
|
127
|
+
pnpm dev
|
|
128
|
+
|
|
129
|
+
# Build for production
|
|
130
|
+
pnpm build
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Scripts
|
|
134
|
+
|
|
135
|
+
| Command | Description |
|
|
136
|
+
|---------|-------------|
|
|
137
|
+
| `pnpm dev` | Run CLI in development mode with tsx |
|
|
138
|
+
| `pnpm start` | Run compiled CLI |
|
|
139
|
+
| `pnpm build` | Build for production with tsup |
|
|
140
|
+
| `pnpm lint` | Run ESLint |
|
|
141
|
+
|
|
142
|
+
## 🤝 Contributing
|
|
143
|
+
|
|
144
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
145
|
+
|
|
146
|
+
## � License
|
|
147
|
+
|
|
148
|
+
MIT License - see the [LICENSE](LICENSE) file for details.
|
|
149
|
+
|
|
150
|
+
## 👤 Author
|
|
151
|
+
|
|
152
|
+
**Mostafa Khaled**
|
|
153
|
+
|
|
154
|
+
- GitHub: [@M0stafa-Khaled](https://github.com/M0stafa-Khaled)
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
<p align="center">Built with ❤️ for the React community</p>
|
package/bin/index.js
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import*as ie from"@clack/prompts";import{program as Re}from"commander";import*as c from"@clack/prompts";import M from"picocolors";var _=async e=>{c.intro(M.bold("\u{1F680} Welcome to crvp CLI!")),c.note(`Interactive scaffolding for modern React + Vite projects
|
|
3
|
+
with Tailwind, Redux, React Query, etc.`,"crvp v0.1.0");let r,t=d=>/^(?:[a-z0-9]+(?:-[a-z0-9]+)*)$/.test(d),a=d=>d==="."||d==="..";if(e&&e.trim()!==""){let d=e.trim();if(!a(d)&&!t(d))return c.log.error("Invalid project name. Use lowercase letters, numbers, hyphens, or '.' / '..'"),null;r=d,c.log.info(`Using project name from argument: ${M.cyan(r)}`)}else{let d=await c.text({message:"Project name (folder name):",placeholder:"my-crvp-app",defaultValue:"my-crvp-app",validate:$=>{let w=$?.trim();if(w&&!a(w)&&!t(w))return"Use lowercase letters, numbers, hyphens, or '.' / '..'"}});if(c.isCancel(d))return c.cancel("Operation cancelled."),null;let k=d.trim();r=k===""?"my-crvp-app":k,c.log.info(`Using project name: ${M.cyan(r)}`)}let s=await c.select({message:"Preferred package manager:",options:[{value:"pnpm",label:"pnpm (recommended - fastest)"},{value:"npm",label:"npm"},{value:"yarn",label:"Yarn"},{value:"bun",label:"Bun"}],initialValue:"pnpm"});if(c.isCancel(s))return c.cancel("Operation cancelled."),null;let o=await c.confirm({message:"Use TypeScript?",initialValue:!0});if(c.isCancel(o))return c.cancel("Operation cancelled."),null;let n=await c.confirm({message:"Use React Compiler?",initialValue:!1});if(c.isCancel(n))return c.cancel("Operation cancelled."),null;let p=await c.confirm({message:"Use Tailwind CSS?",initialValue:!0});if(c.isCancel(p))return c.cancel("Operation cancelled."),null;let u=await c.confirm({message:"Use React Router?",initialValue:!0});if(c.isCancel(u))return c.cancel("Operation cancelled."),null;let l=await c.confirm({message:"Use React Query (TanStack) + Axios?",initialValue:!0});if(c.isCancel(l))return c.cancel("Operation cancelled."),null;let i=await c.confirm({message:"Use Redux Toolkit?",initialValue:!1});if(c.isCancel(i))return c.cancel("Operation cancelled."),null;let m=await c.confirm({message:"Use react-hook-form + zod?",initialValue:!0});if(c.isCancel(m))return c.cancel("Operation cancelled."),null;let h=await c.confirm({message:"Setup ESLint + Prettier?",initialValue:!0});if(c.isCancel(h))return c.cancel("Operation cancelled."),null;let C=await c.select({message:"Folder structure style:",options:[{value:"simple",label:"Simple (flat, good for small/medium projects)"},{value:"scalable",label:"Scalable (feature-based, for larger projects)"}],initialValue:"simple"});return c.isCancel(C)?(c.cancel("Operation cancelled."),null):(c.outro(M.green("All answers collected! Starting scaffolding... \u{1F389}")),{projectName:r,packageManager:s,useTypescript:o,useReactCompiler:n,useTailwind:p,useRouter:u,useReactQueryAxios:l,useRedux:i,useRhformZod:m,useEslintPrettier:h,folderStructure:C})};import.meta.url===`file://${process.argv[1]}`&&(async()=>{let e=await _();e&&console.log(`
|
|
4
|
+
Collected answers:`,e)})();import oe from"picocolors";import I from"fs/promises";import Ae from"path";import*as g from"@clack/prompts";import N from"picocolors";var ne=async e=>{let r=e.trim()||"my-crvp-app";for(;;){let t=Ae.resolve(process.cwd(),r);try{if(await I.access(t),(await I.readdir(t)).length===0)return t;g.note(`The directory ${N.cyan(r)} already exists and contains files.`,"Directory Conflict");let s=await g.select({message:"How do you want to proceed?",options:[{value:"overwrite",label:"Overwrite (delete existing content)"},{value:"new-name",label:"Choose a different name"},{value:"cancel",label:"Cancel"}],initialValue:"new-name"});if(g.isCancel(s)||s==="cancel")return g.cancel("Project creation cancelled."),null;if(s==="overwrite"){let n=await g.confirm({message:`Delete all files in ${N.cyan(r)}? This action cannot be undone.`,initialValue:!1});return g.isCancel(n)||!n?(g.cancel("Overwrite cancelled."),null):(g.log.warn("Deleting existing directory contents..."),await I.rm(t,{recursive:!0,force:!0}),await I.mkdir(t,{recursive:!0}),g.log.success(`Directory prepared: ${N.cyan(r)}`),t)}let o=await g.text({message:"Enter a new project name:",placeholder:"my-crvp-app-2",validate:n=>{let p=n?.trim();if(!p)return"Name is required";let u=i=>i==="."||i==="..",l=i=>/^(?:[a-z0-9]+(?:-[a-z0-9]+)*)$/.test(i);if(!u(p)&&!l(p))return"Use lowercase letters, numbers, hyphens, or '.' / '..'"}});if(g.isCancel(o))return g.cancel("Project creation cancelled."),null;r=o.trim()}catch(a){return a.code==="ENOENT"?(await I.mkdir(t,{recursive:!0}),g.log.success(`Created directory: ${N.cyan(r)}`),t):(g.log.error(`Error: ${a.message}`),null)}}};import{execa as Te}from"execa";import*as f from"@clack/prompts";import P from"fs/promises";import R from"path";import*as ce from"@clack/prompts";import je from"picocolors";var y=async(e,r,t,a="Failed")=>{let s=ce.spinner();s.start(e);try{let o=await t();return s.stop(r),o}catch(o){throw s.stop(`${je.red(a)}: ${o.message}`),o}};import{execa as Se}from"execa";import*as T from"@clack/prompts";import F from"fs/promises";import le from"path";var W=async(e,r)=>{if(!e.useTailwind)return!0;let t=e.packageManager,a=["tailwindcss","@tailwindcss/vite"],s=t,o=[t==="npm"?"install":"add","-D",...a];if(!await y(`Installing Tailwind CSS using ${t}...`,"Tailwind CSS installed successfully",async()=>(await Se(s,o,{cwd:r}),!0),"Failed to install Tailwind CSS"))return!1;let p=e.useTypescript?".ts":".js",u=le.join(r,`vite.config${p}`);try{await F.access(u);let i=await F.readFile(u,"utf-8");i.includes("tailwindcss from")||(i=`import tailwindcss from "@tailwindcss/vite";
|
|
5
|
+
`+i),i.includes("tailwindcss()")||(i=i.replace(/plugins:\s*\[/,"plugins: [tailwindcss(),")),await F.writeFile(u,i),T.log.success(`Updated vite.config${p} with Tailwind plugin.`)}catch(i){return T.log.error(`Failed to update vite.config${p}: ${i.message}`),!1}let l=le.join(r,"src/index.css");try{await F.access(l),(await F.readFile(l,"utf-8")).includes('@import "tailwindcss"')?T.log.info("Tailwind already imported in src/index.css."):(await F.writeFile(l,`@import "tailwindcss";
|
|
6
|
+
`),T.log.success("Added Tailwind import to src/index.css."))}catch(i){return T.log.error(`Failed to update src/index.css: ${i.message}`),!1}return!0};import{execa as ve}from"execa";import*as H from"@clack/prompts";import L from"fs/promises";import pe from"path";var G=async(e,r)=>{if(!e.useReactCompiler)return!0;let t=e.packageManager,a=["react-compiler-runtime@latest","babel-plugin-react-compiler@latest","eslint-plugin-react-compiler@latest"],s=t,o=["add","-D",...a];if(t==="yarn"&&(s="yarn"),t==="bun"&&(s="bun"),!await y(`Installing React Compiler using ${t}...`,"React Compiler packages installed",async()=>(await ve(s,o,{cwd:r}),!0),"Failed to install React Compiler packages"))return!1;let p=e.useTypescript?".ts":".js",u=pe.join(r,`vite.config${p}`);try{await L.access(u);let i=await L.readFile(u,"utf-8"),m=/react\s*\([^)]*\)\s*,?/g;i=i.replace(m,""),i=i.replace(/plugins:\s*\[/,`plugins: [
|
|
7
|
+
react({
|
|
8
|
+
babel: {
|
|
9
|
+
plugins: ["babel-plugin-react-compiler"],
|
|
10
|
+
},
|
|
11
|
+
}),`),await L.writeFile(u,i.trim()),H.log.success(`Updated vite.config${p} with React Compiler config.`)}catch(i){return H.log.error(`Failed to update vite.config${p}: ${i.message}`),!1}let l=pe.join(r,"eslint.config.js");try{try{await L.access(l)}catch{return!0}let i=await L.readFile(l,"utf-8");i.includes('from "eslint-plugin-react-compiler"')||(i=`import reactCompiler from "eslint-plugin-react-compiler";
|
|
12
|
+
`+i),!i.includes('"react-compiler"')&&!i.includes("'react-compiler'")&&(i=i.replace(/plugins:\s*\[([^\]]*)\]/,(h,C)=>{let d=C.trim();return`plugins: [${d?`${d}, "react-compiler"`:'"react-compiler"'}]`})),i.includes("reactCompiler.configs.recommended")||(i=i.replace(/extends:\s*\[([^\]]*)\]/,(h,C)=>{let d=C.trim();return`extends: [${d?`${d} reactCompiler.configs.recommended`:"reactCompiler.configs.recommended"}]`}))}catch(i){H.log.warn(`Failed to update ESLint config: ${i.message}. Skipping React Compiler ESLint rules. Add manually if needed.`)}return!0};import A from"fs/promises";import z from"path";import*as q from"@clack/prompts";var K=async(e,r)=>{let t=e.folderStructure;q.note(`Setting up ${t} folder structure...`,"Structure");let a=e.useTypescript?"ts":"js",s=e.useTypescript?"tsx":"jsx",o=z.join(r,"src");try{try{await A.stat(`${o}/App.css`),await A.unlink(`${o}/App.css`)}catch{}try{await A.stat(`${o}/App.${s}`),await A.unlink(`${o}/App.${s}`)}catch{}if(await A.access(o),t==="simple"){let n=["components","components/ui",...e.useRedux?["store"]:[],"pages","lib","hooks",...e.useRouter?["routes"]:[],...e.useRhformZod?["validations"]:[],...e.useTypescript?["types"]:[]];for(let u of n){let l=z.join(o,u);await A.mkdir(l,{recursive:!0})}let p=[...e.useTypescript?["types/index.d.ts"]:[]];for(let u of p){let l=z.join(o,u);await A.writeFile(l,"")}}else{let n=["app",...e.useRedux?["app/store"]:[],"features","features/auth/pages","features/auth/components",...e.useTypescript?["features/auth/types"]:[],"shared/components/ui","shared/lib","shared/hooks","shared/utils",...e.useTypescript?["shared/types"]:[]];for(let u of n){let l=z.join(o,u);await A.mkdir(l,{recursive:!0})}let p=[...e.useRouter?[`app/router.${s}`]:[],`features/auth/api.${a}`,`features/auth/layout.${s}`,...e.useTypescript?["features/auth/types.d.ts"]:[],...e.useRouter?[`features/auth/routes.${s}`]:[],...e.useRhformZod?[`features/auth/schema.${a}`]:[],...e.useTypescript?["shared/types/index.d.ts"]:[],`shared/api.${a}`,...e.useReactQueryAxios?[`shared/queriesAndMutations.${a}`]:[]];for(let u of p){let l=z.join(o,u);await A.writeFile(l,"")}}return q.log.success(`${t} folder structure setup complete.`),!0}catch(n){return q.log.error(`Failed to setup folder structure: ${n.message}`),!1}};import{execa as Pe}from"execa";import*as O from"@clack/prompts";import ue from"fs/promises";import Z from"path";var Y=async(e,r)=>{if(!e.useRedux)return!0;O.note("Setting up Redux Toolkit...","Redux");let t=e.packageManager,a=e.useTypescript?"ts":"js",s=Z.join(r,"src"),o=["@reduxjs/toolkit","react-redux"],n=t,p=[t==="npm"?"install":"add",...o];if(!await y(`Installing Redux Toolkit & react-redux using ${t}...`,"Redux packages installed",async()=>(await Pe(n,p,{cwd:r}),!0),"Failed to install Redux packages"))return!1;let l=e.folderStructure==="simple",i=l?Z.join(s,"store"):Z.join(s,"app/store"),m=Z.join(i,`index.${a}`);try{await ue.mkdir(i,{recursive:!0});let h=`import { configureStore } from "@reduxjs/toolkit";
|
|
13
|
+
${e.useTypescript?`import {
|
|
14
|
+
type TypedUseSelectorHook,
|
|
15
|
+
useDispatch,
|
|
16
|
+
useSelector,
|
|
17
|
+
} from "react-redux";`:""}
|
|
18
|
+
|
|
19
|
+
export const store = configureStore({
|
|
20
|
+
reducer: {
|
|
21
|
+
// Add reducers here
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
${e.useTypescript?`export type RootState = ReturnType<typeof store.getState>;
|
|
26
|
+
export type AppDispatch = typeof store.dispatch;
|
|
27
|
+
|
|
28
|
+
export const useAppDispatch: () => AppDispatch = useDispatch;
|
|
29
|
+
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;`:""}
|
|
30
|
+
|
|
31
|
+
`;await ue.writeFile(m,h),O.log.success(`Created ${l?"store":"app/store"}/index.${a} (basic store)`)}catch(h){return O.log.error(`Failed to create store: ${h.message}`),!1}return!0};import{execa as $e}from"execa";import*as j from"@clack/prompts";import Q from"fs/promises";import v from"path";var X=async(e,r)=>{if(!e.useReactQueryAxios)return!0;j.note("Setting up React Query + Axios...","React Query");let t=e.packageManager,a=e.useTypescript?"ts":"js",s=v.join(r,"src"),o=["@tanstack/react-query","axios"],n=t,p=["add",...o];if(t==="yarn"&&(n="yarn"),t==="bun"&&(n="bun"),!await y(`Installing React Query + Axios using ${t}...`,"React Query & Axios installed",async()=>(await $e(n,p,{cwd:r}),!0),"Failed to install React Query packages"))return!1;let l=e.folderStructure==="simple",i=l?v.join(s,"lib"):v.join(s,"shared/lib"),m=v.join(i,`api.${a}`);try{await Q.mkdir(i,{recursive:!0}),await Q.writeFile(m,`import axios from "axios";
|
|
32
|
+
|
|
33
|
+
export const api = axios.create({
|
|
34
|
+
baseURL: import.meta.env.VITE_API_URL || "http://localhost:3000",
|
|
35
|
+
timeout: 10000,
|
|
36
|
+
headers: {
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Request interceptor to add auth token
|
|
42
|
+
api.interceptors.request.use(
|
|
43
|
+
(config) => {
|
|
44
|
+
const token = localStorage.getItem("token");
|
|
45
|
+
if (token) {
|
|
46
|
+
config.headers.Authorization = \`Bearer \${token}\`;
|
|
47
|
+
}
|
|
48
|
+
return config;
|
|
49
|
+
},
|
|
50
|
+
(error) => {
|
|
51
|
+
return Promise.reject(error);
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Response interceptor to handle errors
|
|
56
|
+
api.interceptors.response.use(
|
|
57
|
+
(response) => response,
|
|
58
|
+
(error) => {
|
|
59
|
+
if (error.response?.status === 401) {
|
|
60
|
+
// Handle unauthorized - redirect to login or refresh token
|
|
61
|
+
localStorage.removeItem("token");
|
|
62
|
+
window.location.href = "/auth/login";
|
|
63
|
+
}
|
|
64
|
+
return Promise.reject(error);
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
export default api;
|
|
69
|
+
`),j.log.success(`Created ${l?"lib":"shared/lib"}/api.${a}`)}catch(d){return j.log.error(`Failed to create api instance: ${d.message}`),!1}let h=l?v.join(s,"lib"):v.join(s,"shared/lib"),C=v.join(h,`queryClient.${a}`);try{await Q.mkdir(h,{recursive:!0}),await Q.writeFile(C,`import { QueryClient } from "@tanstack/react-query";
|
|
70
|
+
|
|
71
|
+
export const queryClient = new QueryClient({
|
|
72
|
+
defaultOptions: {
|
|
73
|
+
queries: {
|
|
74
|
+
retry: 1,
|
|
75
|
+
staleTime: 1000 * 60, // 1 minute
|
|
76
|
+
gcTime: 1000 * 60 * 5, // 5 minutes
|
|
77
|
+
refetchOnWindowFocus: true,
|
|
78
|
+
refetchOnReconnect: true,
|
|
79
|
+
refetchOnMount: true,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
`),j.log.success(`Created ${l?"lib":"shared/lib"}/queryClient.${a}`)}catch(d){return j.log.error(`Failed to create queryClient: ${d.message}`),!1}if(!l){let d=v.join(s,`shared/queriesAndMutations.${a}`);try{let k=`import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
84
|
+
import { api } from "./api";
|
|
85
|
+
|
|
86
|
+
// Example query hook
|
|
87
|
+
export const useExampleQuery = () => {
|
|
88
|
+
return useQuery({
|
|
89
|
+
queryKey: ["example"],
|
|
90
|
+
queryFn: async () => {
|
|
91
|
+
const { data } = await api.get("/example");
|
|
92
|
+
return data;
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Example mutation hook
|
|
98
|
+
export const useExampleMutation = () => {
|
|
99
|
+
const queryClient = useQueryClient();
|
|
100
|
+
|
|
101
|
+
return useMutation({
|
|
102
|
+
mutationFn: async (payload${e.useTypescript?": unknown":""}) => {
|
|
103
|
+
const { data } = await api.post("/example", payload);
|
|
104
|
+
return data;
|
|
105
|
+
},
|
|
106
|
+
onSuccess: () => {
|
|
107
|
+
// Invalidate and refetch
|
|
108
|
+
queryClient.invalidateQueries({ queryKey: ["example"] });
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
`;await Q.writeFile(d,k),j.log.success(`Created shared/queriesAndMutations.${a}`)}catch(k){return j.log.error(`Failed to create queriesAndMutations: ${k.message}`),!1}}return!0};import{execa as be}from"execa";import*as b from"@clack/prompts";import me from"fs/promises";import ee from"path";var te=async(e,r)=>{if(!e.useRhformZod)return!0;b.note("Setting up React Hook Form + Zod...","React Hook Form");let t=e.packageManager,a=e.useTypescript?"ts":"js",s=ee.join(r,"src"),o=["react-hook-form","zod","@hookform/resolvers"],n=t,p=["add",...o];if(t==="yarn"&&(n="yarn"),t==="bun"&&(n="bun"),!await y(`Installing React Hook Form & Zod using ${t}...`,"React Hook Form & Zod installed",async()=>(await be(n,p,{cwd:r}),!0),"Failed to install React Hook Form & Zod packages"))return!1;if(e.folderStructure==="simple"){let i=ee.join(s,`validations/index.${a}`);try{let m=e.useTypescript?`import { z } from "zod";
|
|
113
|
+
|
|
114
|
+
// Example validation schema
|
|
115
|
+
export const loginSchema = z
|
|
116
|
+
.object({
|
|
117
|
+
email: z.string().email("Invalid email address"),
|
|
118
|
+
password: z.string().min(6, "Password must be at least 6 characters"),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export type LoginFormData = z.infer<typeof loginSchema>;
|
|
122
|
+
|
|
123
|
+
// Add more validation schemas here
|
|
124
|
+
`:`import { z } from "zod";
|
|
125
|
+
|
|
126
|
+
// Example validation schema
|
|
127
|
+
export const loginSchema = z
|
|
128
|
+
.object({
|
|
129
|
+
email: z.string().email("Invalid email address"),
|
|
130
|
+
password: z.string().min(6, "Password must be at least 6 characters"),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Add more validation schemas here
|
|
134
|
+
`;await me.writeFile(i,m),b.log.success(`Created validations/index.${a}`)}catch(m){return b.log.error(`Failed to create validations: ${m.message}`),!1}}else{let i=ee.join(s,`features/auth/schema.${a}`);try{let m=e.useTypescript?`import { z } from "zod";
|
|
135
|
+
|
|
136
|
+
// Auth feature validation schemas
|
|
137
|
+
export const loginSchema = z
|
|
138
|
+
.object({
|
|
139
|
+
email: z.string().email("Invalid email address"),
|
|
140
|
+
password: z.string().min(6, "Password must be at least 6 characters"),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
export const registerSchema = z
|
|
144
|
+
.object({
|
|
145
|
+
email: z.string().email("Invalid email address"),
|
|
146
|
+
password: z.string().min(6, "Password must be at least 6 characters"),
|
|
147
|
+
confirmPassword: z.string(),
|
|
148
|
+
})
|
|
149
|
+
.refine((data) => data.password === data.confirmPassword, {
|
|
150
|
+
message: "Passwords don't match",
|
|
151
|
+
path: ["confirmPassword"],
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
export type LoginFormData = z.infer<typeof loginSchema>;
|
|
155
|
+
export type RegisterFormData = z.infer<typeof registerSchema>;
|
|
156
|
+
|
|
157
|
+
// Add more validation schemas here
|
|
158
|
+
`:`import { z } from "zod";
|
|
159
|
+
|
|
160
|
+
// Auth feature validation schemas
|
|
161
|
+
export const loginSchema = z
|
|
162
|
+
.object({
|
|
163
|
+
email: z.string().email("Invalid email address"),
|
|
164
|
+
password: z.string().min(6, "Password must be at least 6 characters"),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
export const registerSchema = z
|
|
168
|
+
.object({
|
|
169
|
+
email: z.string().email("Invalid email address"),
|
|
170
|
+
password: z.string().min(6, "Password must be at least 6 characters"),
|
|
171
|
+
confirmPassword: z.string(),
|
|
172
|
+
})
|
|
173
|
+
.refine((data) => data.password === data.confirmPassword, {
|
|
174
|
+
message: "Passwords don't match",
|
|
175
|
+
path: ["confirmPassword"],
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Add more validation schemas here
|
|
179
|
+
`;await me.writeFile(i,m),b.log.success(`Created features/auth/schema.${a}`)}catch(m){return b.log.error(`Failed to create schema: ${m.message}`),!1}}return!0};import{execa as de}from"execa";import*as x from"@clack/prompts";import U from"fs/promises";import D from"path";var re=async(e,r)=>{if(!e.useEslintPrettier)return!0;let t=e.packageManager,a=e.useTypescript,s=["eslint","eslint-plugin-react","eslint-plugin-react-hooks","eslint-plugin-jsx-a11y","eslint-plugin-import","eslint-config-prettier","eslint-plugin-prettier","prettier",...e.useTailwind?["prettier-plugin-tailwindcss"]:[]];a&&s.push("@typescript-eslint/eslint-plugin","@typescript-eslint/parser","typescript-eslint");let o=t,n;if(t==="npm"?n=["install","-D",...s]:t==="yarn"?n=["add","-D",...s]:t==="bun"?n=["add","-d",...s]:n=["add","-D",...s],!await y(`Installing ESLint + Prettier using ${t}...`,"ESLint & Prettier installed successfully",async()=>(await de(o,n,{cwd:r}),!0),"Failed to install ESLint/Prettier packages"))return!1;let u=D.join(r,"eslint.config.js"),l=a?`import js from "@eslint/js";
|
|
180
|
+
import globals from "globals";
|
|
181
|
+
import reactHooks from "eslint-plugin-react-hooks";
|
|
182
|
+
import reactRefresh from "eslint-plugin-react-refresh";
|
|
183
|
+
import tseslint from "typescript-eslint";
|
|
184
|
+
import prettier from "eslint-plugin-prettier/recommended";
|
|
185
|
+
import react from "eslint-plugin-react";
|
|
186
|
+
import jsxA11y from "eslint-plugin-jsx-a11y";
|
|
187
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
188
|
+
|
|
189
|
+
export default defineConfig([
|
|
190
|
+
globalIgnores(["dist"]),
|
|
191
|
+
{
|
|
192
|
+
files: ["**/*.{ts,tsx}"],
|
|
193
|
+
extends: [
|
|
194
|
+
js.configs.recommended,
|
|
195
|
+
tseslint.configs.recommended,
|
|
196
|
+
reactHooks.configs.flat.recommended,
|
|
197
|
+
reactRefresh.configs.vite,
|
|
198
|
+
prettier,
|
|
199
|
+
],
|
|
200
|
+
languageOptions: {
|
|
201
|
+
ecmaVersion: 2020,
|
|
202
|
+
globals: globals.browser,
|
|
203
|
+
parser: tseslint.parser,
|
|
204
|
+
parserOptions: {
|
|
205
|
+
project: "./tsconfig.app.json",
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
plugins: {
|
|
209
|
+
react,
|
|
210
|
+
"jsx-a11y": jsxA11y,
|
|
211
|
+
},
|
|
212
|
+
settings: {
|
|
213
|
+
react: {
|
|
214
|
+
version: "detect",
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
rules: {
|
|
218
|
+
"react-refresh/only-export-components": [
|
|
219
|
+
"warn",
|
|
220
|
+
{ allowConstantExport: true },
|
|
221
|
+
],
|
|
222
|
+
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
|
|
223
|
+
"@typescript-eslint/explicit-function-return-type": "off",
|
|
224
|
+
"@typescript-eslint/no-explicit-any": "warn",
|
|
225
|
+
"jsx-a11y/alt-text": "warn",
|
|
226
|
+
"jsx-a11y/anchor-has-content": "warn",
|
|
227
|
+
"jsx-a11y/anchor-is-valid": "warn",
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
]);
|
|
231
|
+
`:`import js from "@eslint/js";
|
|
232
|
+
import globals from "globals";
|
|
233
|
+
import reactHooks from "eslint-plugin-react-hooks";
|
|
234
|
+
import reactRefresh from "eslint-plugin-react-refresh";
|
|
235
|
+
import prettier from "eslint-plugin-prettier/recommended";
|
|
236
|
+
import react from "eslint-plugin-react";
|
|
237
|
+
import jsxA11y from "eslint-plugin-jsx-a11y";
|
|
238
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
239
|
+
|
|
240
|
+
export default defineConfig([
|
|
241
|
+
globalIgnores(["dist"]),
|
|
242
|
+
{
|
|
243
|
+
files: ["**/*.{js,jsx}"],
|
|
244
|
+
extends: [js.configs.recommended, reactHooks.configs.flat.recommended, reactRefresh.configs.vite, prettier],
|
|
245
|
+
languageOptions: {
|
|
246
|
+
ecmaVersion: 2020,
|
|
247
|
+
globals: globals.browser,
|
|
248
|
+
parserOptions: {
|
|
249
|
+
ecmaFeatures: {
|
|
250
|
+
jsx: true,
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
plugins: {
|
|
255
|
+
react,
|
|
256
|
+
"jsx-a11y": jsxA11y,
|
|
257
|
+
},
|
|
258
|
+
settings: {
|
|
259
|
+
react: {
|
|
260
|
+
version: "detect",
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
rules: {
|
|
264
|
+
"react-refresh/only-export-components": [
|
|
265
|
+
"warn",
|
|
266
|
+
{ allowConstantExport: true },
|
|
267
|
+
],
|
|
268
|
+
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
|
|
269
|
+
"jsx-a11y/alt-text": "warn",
|
|
270
|
+
"jsx-a11y/anchor-has-content": "warn",
|
|
271
|
+
"jsx-a11y/anchor-is-valid": "warn",
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
]);
|
|
275
|
+
`;try{await U.writeFile(u,l),x.log.success("Created eslint.config.js")}catch(w){return x.log.error(`Failed to create ESLint config: ${w.message}`),!1}let i=D.join(r,".prettierrc"),m={semi:!0,singleQuote:!1,tabWidth:2,trailingComma:"es5",...e.useTailwind&&{plugins:["prettier-plugin-tailwindcss"]}};try{await U.writeFile(i,JSON.stringify(m,null,2)),x.log.success("Created .prettierrc")}catch(w){return x.log.error(`Failed to create Prettier config: ${w.message}`),!1}let h=D.join(r,".prettierignore"),C=`# Dependencies
|
|
276
|
+
node_modules
|
|
277
|
+
.pnpm-store
|
|
278
|
+
|
|
279
|
+
# Build output
|
|
280
|
+
dist
|
|
281
|
+
build
|
|
282
|
+
*.min.js
|
|
283
|
+
*.min.css
|
|
284
|
+
|
|
285
|
+
# Logs
|
|
286
|
+
*.log
|
|
287
|
+
|
|
288
|
+
# Environment files
|
|
289
|
+
.env
|
|
290
|
+
.env.local
|
|
291
|
+
.env.*.local
|
|
292
|
+
|
|
293
|
+
# Cache
|
|
294
|
+
.cache
|
|
295
|
+
*.cache
|
|
296
|
+
|
|
297
|
+
# Public assets
|
|
298
|
+
public
|
|
299
|
+
|
|
300
|
+
# Lock files
|
|
301
|
+
package-lock.json
|
|
302
|
+
yarn.lock
|
|
303
|
+
pnpm-lock.yaml
|
|
304
|
+
bun.lockb
|
|
305
|
+
`;try{await U.writeFile(h,C),x.log.success("Created .prettierignore")}catch(w){return x.log.error(`Failed to create .prettierignore: ${w.message}`),!1}let d=D.join(r,"package.json");try{let w=await U.readFile(d,"utf-8"),B=JSON.parse(w);B.scripts={...B.scripts,lint:"eslint . --ext .ts,.tsx,.js,.jsx","lint:fix":"eslint . --ext .ts,.tsx,.js,.jsx --fix",format:"prettier --write .","format:check":"prettier --check ."},await U.writeFile(d,JSON.stringify(B,null,2)),x.log.success("Added lint and format scripts to package.json")}catch(w){return x.log.error(`Failed to update package.json: ${w.message}`),!1}let k=["globals"],$;t==="npm"?$=["install","-D",...k]:t==="yarn"?$=["add","-D",...k]:t==="bun"?$=["add","-d",...k]:$=["add","-D",...k];try{await de(o,$,{cwd:r}),x.log.success("Installed ESLint peer dependencies")}catch(w){x.log.warn(`Could not install peer dependencies: ${w.message}`)}return!0};import{execa as Fe}from"execa";import*as S from"@clack/prompts";import V from"fs/promises";import E from"path";var fe=e=>{let r=e.useTypescript,t=e.useRedux,a=e.useReactQueryAxios,s=e.useRouter,o=e.folderStructure==="simple",n=[];t&&(n.push('import { Provider } from "react-redux"'),n.push(`import { store } from "${o?"../store":"./store"}"`)),a&&(n.push('import { QueryClientProvider } from "@tanstack/react-query"'),n.push(`import { queryClient } from "${o?"../lib/queryClient":"../shared/lib/queryClient"}"`)),s&&(n.push('import { RouterProvider } from "react-router"'),n.push(`import { router } from "${o?"../routes":"./router"}"`)),!s&&r&&n.push('import type { ReactNode } from "react"');let p=i=>{let m=i;return a&&(m=`<QueryClientProvider client={queryClient}>
|
|
306
|
+
${m}</QueryClientProvider>`),t&&(m=`<Provider store={store}>
|
|
307
|
+
${m}</Provider>`),m},u=s?" <RouterProvider router={router} />":" {children}",l=s?"":r?"{ children }: { children: ReactNode }":"{ children }";return`${n.join(`
|
|
308
|
+
`)}
|
|
309
|
+
|
|
310
|
+
const Providers = (${l}) => {
|
|
311
|
+
return (
|
|
312
|
+
${p(u)}
|
|
313
|
+
);
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
export default Providers;
|
|
317
|
+
`},ge=e=>{let r=e.folderStructure==="simple",t=e.useRouter;return`import Providers from ${r?'"./components/providers"':'"./app/providers"'};
|
|
318
|
+
|
|
319
|
+
const App = () => {
|
|
320
|
+
return ${t?"<Providers />":"<Providers><div>App</div></Providers>"};
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
export default App;
|
|
324
|
+
`},he=e=>`import { createBrowserRouter } from "react-router";
|
|
325
|
+
|
|
326
|
+
export const router = createBrowserRouter([
|
|
327
|
+
{
|
|
328
|
+
path: "/",
|
|
329
|
+
element: <div>Home</div>,
|
|
330
|
+
children: [
|
|
331
|
+
// Add your routes here
|
|
332
|
+
],
|
|
333
|
+
},
|
|
334
|
+
]);
|
|
335
|
+
`,ye=e=>`import AuthLayout from "./layout";
|
|
336
|
+
|
|
337
|
+
export const authRoutes = [
|
|
338
|
+
{
|
|
339
|
+
path: "auth",
|
|
340
|
+
element: <AuthLayout />,
|
|
341
|
+
children: [
|
|
342
|
+
{
|
|
343
|
+
path: "login",
|
|
344
|
+
element: <div>Login</div>,
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
path: "register",
|
|
348
|
+
element: <div>Register</div>,
|
|
349
|
+
},
|
|
350
|
+
],
|
|
351
|
+
},
|
|
352
|
+
];
|
|
353
|
+
`,we=e=>`import { createBrowserRouter } from "react-router";
|
|
354
|
+
import { authRoutes } from "../features/auth/routes";
|
|
355
|
+
|
|
356
|
+
export const router = createBrowserRouter([
|
|
357
|
+
{
|
|
358
|
+
path: "/",
|
|
359
|
+
element: <div>Home</div>,
|
|
360
|
+
},
|
|
361
|
+
...authRoutes,
|
|
362
|
+
]);
|
|
363
|
+
`,se=e=>`// Global type definitions
|
|
364
|
+
|
|
365
|
+
// Example: User type
|
|
366
|
+
export interface User {
|
|
367
|
+
id: string;
|
|
368
|
+
email: string;
|
|
369
|
+
name: string;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Example: API response type
|
|
373
|
+
export interface ApiResponse<T> {
|
|
374
|
+
data: T;
|
|
375
|
+
message: string;
|
|
376
|
+
success: boolean;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Add more global types here
|
|
380
|
+
`,ke=e=>`// Auth feature types
|
|
381
|
+
|
|
382
|
+
export interface AuthUser {
|
|
383
|
+
id: string;
|
|
384
|
+
email: string;
|
|
385
|
+
name: string;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export interface LoginCredentials {
|
|
389
|
+
email: string;
|
|
390
|
+
password: string;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export interface RegisterCredentials extends LoginCredentials {
|
|
394
|
+
confirmPassword: string;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Add more auth-related types here
|
|
398
|
+
`,xe=e=>{let r=e.useTypescript;return e.useReactQueryAxios?`// Auth feature API calls using Axios
|
|
399
|
+
${r?`import type { AuthUser, LoginCredentials, RegisterCredentials } from "./types";
|
|
400
|
+
import type { ApiResponse } from "../../shared/types";
|
|
401
|
+
import { api } from "../../shared/lib/api";`:'import { api } from "../shared/lib/api";'}
|
|
402
|
+
|
|
403
|
+
// Example API functions
|
|
404
|
+
export const login = async (credentials${r?": LoginCredentials":""})${r?": Promise<ApiResponse<AuthUser>>":""} => {
|
|
405
|
+
const { data } = await api.post("/auth/login", credentials);
|
|
406
|
+
return data;
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
export const register = async (credentials${r?": RegisterCredentials":""})${r?": Promise<ApiResponse<AuthUser>>":""} => {
|
|
410
|
+
const { data } = await api.post("/auth/register", credentials);
|
|
411
|
+
return data;
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// Add more API functions here
|
|
415
|
+
`:`// Auth feature API calls using Fetch
|
|
416
|
+
${r?`import type { AuthUser, LoginCredentials, RegisterCredentials } from "./types";
|
|
417
|
+
import type { ApiResponse } from "../shared/types";`:""}
|
|
418
|
+
|
|
419
|
+
// Example API functions
|
|
420
|
+
export const login = async (credentials${r?": LoginCredentials":""})${r?": Promise<ApiResponse<AuthUser>>":""} => {
|
|
421
|
+
// Implement login API call
|
|
422
|
+
const response = await fetch("/api/auth/login", {
|
|
423
|
+
method: "POST",
|
|
424
|
+
headers: { "Content-Type": "application/json" },
|
|
425
|
+
body: JSON.stringify(credentials),
|
|
426
|
+
});
|
|
427
|
+
return response.json();
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
export const register = async (credentials${r?": RegisterCredentials":""})${r?": Promise<ApiResponse<AuthUser>>":""} => {
|
|
431
|
+
// Implement register API call
|
|
432
|
+
const response = await fetch("/api/auth/register", {
|
|
433
|
+
method: "POST",
|
|
434
|
+
headers: { "Content-Type": "application/json" },
|
|
435
|
+
body: JSON.stringify(credentials),
|
|
436
|
+
});
|
|
437
|
+
return response.json();
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
// Add more API functions here
|
|
441
|
+
`},J=e=>`import { Outlet } from "react-router";
|
|
442
|
+
|
|
443
|
+
const AuthLayout = () => {
|
|
444
|
+
return (
|
|
445
|
+
<div>
|
|
446
|
+
Auth Layout
|
|
447
|
+
<Outlet />
|
|
448
|
+
</div>
|
|
449
|
+
);
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
export default AuthLayout;
|
|
453
|
+
`;var ae=async(e,r)=>{if(!e.useRouter)return!0;S.note("Setting up React Router...","React Router");let t=e.packageManager,a=e.useTypescript?"tsx":"jsx",s=E.join(r,"src"),o=["react-router"],n=t,p=["add",...o];if(t==="npm"?p=["install",...o]:t==="yarn"?p=["add",...o]:t==="bun"&&(p=["add",...o]),!await y(`Installing React Router using ${t}...`,"React Router installed",async()=>(await Fe(n,p,{cwd:r}),!0),"Failed to install React Router"))return!1;let l=e.folderStructure==="simple";try{if(l){let i=E.join(s,"routes");await V.mkdir(i,{recursive:!0});let m=E.join(i,`index.${a}`);await V.writeFile(m,he(e)),S.log.success(`Created routes/index.${a}`)}else{let i=E.join(s,`app/router.${a}`);await V.writeFile(i,we(e)),S.log.success(`Created app/router.${a}`);let m=E.join(s,`features/auth/routes.${a}`);await V.writeFile(m,ye(e)),S.log.success(`Created features/auth/routes.${a}`);let h=E.join(s,`features/auth/layout.${a}`);await V.writeFile(h,J(e)),S.log.success(`Created features/auth/layout.${a}`)}return S.log.success("React Router setup complete!"),!0}catch(i){return S.log.error(`Failed to create router files: ${i.message}`),!1}};var Ce=async(e,r)=>await Ee(e,r)?(f.note("Next steps (dependencies, Tailwind, etc.) will be added gradually."),await W(e,r)?await G(e,r)?await K(e,r)?await ae(e,r)?await Y(e,r)?await X(e,r)?await te(e,r)?await re(e,r)?await Ie(e,r)?!0:(f.log.error("Failed to generate application files."),!1):(f.log.error("Failed to setup ESLint & Prettier."),!1):(f.log.error("Failed to setup react hook form & zod."),!1):(f.log.error("Failed to setup react query & axios."),!1):(f.log.error("Failed to setup redux toolkit."),!1):(f.log.error("Failed to setup React Router."),!1):(f.log.error("Failed to setup folder structure."),!1):(f.log.error("Failed to setup React Compiler. Aborting."),!1):(f.log.error("Failed to setup Tailwind. Aborting."),!1)):(f.log.error("Failed to create Vite project. Aborting."),!1),Ee=async(e,r)=>{let t=e.useTypescript?"react-ts":"react",a=e.packageManager,s,o;return a==="npm"?(s="npm",o=["create","vite@latest",".","--","--template",t,"--no-interactive"]):a==="yarn"?(s="yarn",o=["create","vite",".","--template",t,"--no-interactive"]):a==="bun"?(s="bun",o=["create","vite",".","--template",t,"--no-interactive"]):(s="pnpm",o=["create","vite@latest",".","--template",t,"--no-interactive","--"]),await y(`Creating Vite project using ${a}...`,"Vite project created successfully! \u{1F389}",async()=>{let n=await Te(s,o,{cwd:r,stdio:"pipe"});return n.stderr&&f.log.warn(`Warnings/Logs: ${n.stderr}`),f.log.message(n.stdout||"Done."),!0},"Failed to create Vite project").catch(n=>(f.log.error(`Error details: ${n.message}`),!1))},Ie=async(e,r)=>{f.note("Generating application files...","Files");let t=e.useTypescript?"ts":"js",a=e.useTypescript?"tsx":"jsx",s=R.join(r,"src"),o=e.folderStructure==="simple";try{let n=R.join(s,`App.${a}`);await P.writeFile(n,ge(e)),f.log.success(`Created App.${a}`);let p=o?R.join(s,`components/providers.${a}`):R.join(s,`app/providers.${a}`);if(await P.writeFile(p,fe(e)),f.log.success(`Created ${o?"components":"app"}/providers.${a}`),e.useTypescript)if(o){let u=R.join(s,"types/index.d.ts");await P.writeFile(u,se(e)),f.log.success("Created types/index.d.ts")}else{let u=R.join(s,"shared/types/index.d.ts");await P.writeFile(u,se(e)),f.log.success("Created shared/types/index.d.ts");let l=R.join(s,"features/auth/types.d.ts");await P.writeFile(l,ke(e)),f.log.success("Created features/auth/types.d.ts")}if(!o){let u=R.join(s,`features/auth/api.${t}`);await P.writeFile(u,xe(e)),f.log.success(`Created features/auth/api.${t}`);let l=R.join(s,`features/auth/layout.${a}`);await P.writeFile(l,J(e)),f.log.success(`Created features/auth/layout.${a}`);let i=R.join(s,`shared/api.${t}`),m=e.useReactQueryAxios?`// Re-export API from lib location | OR can add shared api calls here
|
|
454
|
+
export { api } from "./lib/api";
|
|
455
|
+
`:`// Shared API configuration
|
|
456
|
+
export const api = {
|
|
457
|
+
// Add your shared API configuration here
|
|
458
|
+
};
|
|
459
|
+
`;await P.writeFile(i,m),f.log.success(`Created shared/api.${t}`)}return f.log.success("Application files generated successfully!"),!0}catch(n){return f.log.error(`Failed to generate application files: ${n.message}`),!1}};console.log(oe.bold("crvp CLI v0.1.0"));Re.name("crvp").description("Interactive CLI to create modern React + Vite projects").version("0.1.0").argument("[project-name]","Name of the project (folder)").action(async e=>{let t=await _(e);if(t){ie.log.step("Preparing project directory...");let a=await ne(t.projectName);if(!a){console.log(oe.yellow("Project creation cancelled."));return}a&&t&&(await Ce(t,a)||process.exit(1)),ie.log.success(`Ready to scaffold in: ${oe.cyan(a)}`)}});Re.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-rvp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Interactive CLI for creating modern React + Vite projects with shadcn, Tailwind, Redux, etc.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/cli.js",
|
|
7
|
+
"author": "Mostafa Khaled",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"cli",
|
|
10
|
+
"vite",
|
|
11
|
+
"react",
|
|
12
|
+
"create",
|
|
13
|
+
"scaffold",
|
|
14
|
+
"tailwind"
|
|
15
|
+
],
|
|
16
|
+
"bin": {
|
|
17
|
+
"create-rvp": "bin/index.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"bin"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"dev": "tsx src/cli.ts",
|
|
25
|
+
"start": "node dist/cli.js",
|
|
26
|
+
"build": "tsup src/cli.ts --format esm --clean --minify",
|
|
27
|
+
"lint": "eslint . --ext .ts"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@clack/prompts": "^1.0.1",
|
|
35
|
+
"commander": "^14.0.3",
|
|
36
|
+
"execa": "^9.6.1",
|
|
37
|
+
"fs-extra": "^11.3.3",
|
|
38
|
+
"picocolors": "^1.1.1"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^25.3.0",
|
|
42
|
+
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
|
43
|
+
"@typescript-eslint/parser": "^8.56.1",
|
|
44
|
+
"eslint": "^10.0.2",
|
|
45
|
+
"tsup": "^8.5.1",
|
|
46
|
+
"tsx": "^4.21.0",
|
|
47
|
+
"typescript": "^5.9.3"
|
|
48
|
+
}
|
|
49
|
+
}
|