build-app-with 2.0.11 → 2.0.12

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 CHANGED
@@ -1,109 +1,222 @@
1
- # 🚀 Build App With
1
+ # Build App With
2
2
 
3
- **The fastest way to create modern web applications**
3
+ **Interactive CLI for scaffolding production-ready web applications**
4
4
 
5
- An interactive CLI tool that helps you quickly set up production-ready web applications with popular frameworks and features - no complex configuration needed.
5
+ [![npm version](https://img.shields.io/npm/v/build-app-with.svg)](https://www.npmjs.com/package/build-app-with)
6
+ [![license](https://img.shields.io/npm/l/build-app-with.svg)](LICENSE)
7
+ [![node](https://img.shields.io/node/v/build-app-with.svg)](package.json)
6
8
 
7
- ## 🎯 What can you build?
9
+ Choose a framework, pick your features, and get a fully configured project in seconds — no manual setup required.
8
10
 
9
- ### Frontend Applications
10
- - **⚛️ React with Next.js** - Full-stack React framework with server-side rendering
11
- - **⚡ React with Vite** - Lightning-fast React development with modern tooling
11
+ ## Quick Start
12
12
 
13
- ### Backend APIs
14
- - **🚀 Express.js** - Traditional Node.js web server with extensive ecosystem
15
- - **⚡ Fastify** - High-performance Node.js framework for APIs
13
+ ```bash
14
+ # Run directly (no install needed)
15
+ npx build-app-with
16
+
17
+ # Or provide a project name upfront
18
+ npx build-app-with my-app
19
+ ```
16
20
 
17
- ## 🚀 Quick Start
21
+ **Prerequisites:** Node.js 18+ and npm 8+
18
22
 
19
- ### ✨ Recommended: Use directly (no installation needed)
20
23
  ```bash
21
- npx build-app-with my-awesome-app
24
+ npx build-app-with --help # Show usage info
25
+ npx build-app-with --version # Show version
22
26
  ```
23
27
 
24
- ## 💡 How it works
28
+ ## Supported Frameworks
25
29
 
26
- 1. **Choose your framework** - React (Next.js/Vite) or Node.js (Express/Fastify)
27
- 2. **Pick your features** - TypeScript, CSS frameworks, authentication, databases, etc.
28
- 3. **Get your app** - Complete project setup with all files configured and ready to run
30
+ | Framework | Type | Description |
31
+ |-----------|------|-------------|
32
+ | **Next.js** | Full-stack | SSR, API routes, App Router. TypeScript enforced. |
33
+ | **Vite + React** | Frontend | Lightning-fast HMR with optional TypeScript. |
34
+ | **Rsbuild + React** | Frontend | Rust-powered bundler (Rspack). TypeScript enforced. |
35
+ | **Express.js** | Backend | Traditional Node.js web server with extensive middleware ecosystem. |
36
+ | **Fastify** | Backend | High-performance Node.js API framework with plugin architecture. |
29
37
 
30
- ## Available Features
38
+ ## Preset System
31
39
 
32
- ### 🎨 Styling & UI
33
- - **CSS Frameworks**: Tailwind CSS, Bootstrap, Material-UI, Chakra UI, shadcn/ui
34
- - **Components**: Pre-built component libraries and design systems
40
+ When you choose **Vite + React** or **Rsbuild + React**, you pick a preset that determines default features:
35
41
 
36
- ### 🔐 Authentication
37
- - **Options**: JWT tokens, NextAuth.js, Clerk, Auth0
38
- - **Ready-to-use**: Login/signup pages and user management
42
+ | Preset | What's Included |
43
+ |--------|----------------|
44
+ | **Starter** | React + CSS only. Minimal starting point. |
45
+ | **Standard** (default) | React Router DOM, Zustand, React Hook Form, React Icons, React Toastify |
46
+ | **Full** | Everything in Standard + Framer Motion, React Table, React Helmet, react-i18next |
47
+ | **Custom** | Choose every feature manually |
39
48
 
40
- ### 💾 Databases
41
- - **SQL**: PostgreSQL, MySQL, SQLite
42
- - **NoSQL**: MongoDB
43
- - **ORMs**: Prisma, Mongoose, Sequelize
49
+ Standard and Full presets let you add more features on top of the defaults. Next.js and backend frameworks use a Default / Customize toggle instead.
44
50
 
45
- ### 🛠️ Development Tools
46
- - **TypeScript**: Full type safety and IntelliSense
47
- - **Testing**: Jest test framework with example tests
48
- - **Code Quality**: ESLint, Prettier, pre-commit hooks
49
- - **Docker**: Production-ready containerization
51
+ ## Features Reference
50
52
 
51
- ### 🚀 Production Features
52
- - **Security**: CORS, Helmet, rate limiting, input validation
53
- - **Logging**: Structured logging with Winston
54
- - **API Docs**: Automatic Swagger/OpenAPI documentation
55
- - **Performance**: Optimized builds and caching
53
+ ### CSS Frameworks (Frontend)
56
54
 
57
- ## 📖 Example Usage
55
+ Tailwind CSS, Bootstrap, Material-UI (MUI), Chakra UI, shadcn/ui, Mantine, Styled Components, or Vanilla CSS.
58
56
 
59
- ```bash
60
- # Create a Next.js app with TypeScript and Tailwind
61
- npx build-app-with my-next-app
57
+ Available for Vite, Rsbuild (all non-Starter presets), and Next.js (Customize mode).
58
+
59
+ ### Authentication
60
+
61
+ **Frontend (Next.js, Vite, Rsbuild):**
62
+ Clerk, NextAuth.js (Next.js only), Auth0, Firebase Auth
63
+
64
+ **Backend (Express, Fastify):**
65
+ JWT, Session-based, OAuth (Passport.js)
66
+
67
+ ### Project Structure
68
+
69
+ **React frontends (Vite, Rsbuild):**
70
+
71
+ | Structure | Description |
72
+ |-----------|-------------|
73
+ | Simple | Flat `src/` with basic components — good for prototypes |
74
+ | Feature-based | Organized by feature (`features/auth/`, `features/dashboard/`) with shared hooks, services, and utils |
75
+ | Domain-driven | Enterprise pattern with `domains/`, `shared/`, `app/`, and `infrastructure/` layers |
76
+
77
+ **Express:**
78
+ Simple, Modular (by feature), or Layered (Controller-Service-Repository)
79
+
80
+ **Fastify:**
81
+ Simple, Modular (by feature), or Plugin-based (Fastify plugins)
82
+
83
+ ### React Feature Categories
84
+
85
+ These 7 categories are available for Vite + React and Rsbuild + React projects (Custom preset, or as additions to Standard/Full):
62
86
 
63
- # Create an Express API with MongoDB and JWT auth
64
- npx build-app-with my-api
87
+ | Category | Options |
88
+ |----------|---------|
89
+ | **Routing** | React Router DOM |
90
+ | **State Management** | Redux Toolkit, Zustand, MobX, Recoil |
91
+ | **Forms** | React Hook Form, Formik |
92
+ | **Animations** | Framer Motion, React Spring, React Transition Group |
93
+ | **Drag & Drop** | React DnD, React Beautiful DnD |
94
+ | **UI Libraries** | Ant Design, Bootstrap, MUI, Chakra UI, shadcn/ui, Mantine |
95
+ | **Utilities** | React Icons, React Toastify, React Table, React Select, React Helmet, react-i18next, Storybook |
96
+
97
+ ### Backend Features (Express / Fastify)
98
+
99
+ | Category | Options |
100
+ |----------|---------|
101
+ | **Databases** | MongoDB, PostgreSQL, MySQL, SQLite |
102
+ | **ORMs** | Mongoose (MongoDB), Prisma (PostgreSQL/SQLite), Sequelize (MySQL) |
103
+ | **Security** | CORS, Helmet (Express), Rate limiting (Fastify) |
104
+ | **Logging** | Morgan (request logging), Winston (structured logging) |
105
+ | **Validation** | Express Validator / Fastify validation |
106
+ | **API Docs** | Swagger / OpenAPI |
107
+ | **Environment** | dotenv (.env support) |
108
+ | **Testing** | Jest test setup with example tests |
109
+ | **Docker** | Dockerfile and docker-compose configuration |
110
+
111
+ Fastify also supports **WebSocket** and **GraphQL** as additional feature choices.
112
+
113
+ ## Interactive Flow
114
+
115
+ Running `npx build-app-with` walks you through these steps:
65
116
 
66
- # Create a Vite React app with all the bells and whistles
67
- npx build-app-with my-react-app
68
117
  ```
118
+ ? Choose your app framework:
119
+ > Next.js (React Full-stack)
120
+ Vite + React (Frontend)
121
+ Rsbuild + React (Frontend)
122
+ Express.js (Node.js Backend)
123
+ Fastify (Node.js Backend)
124
+
125
+ ? Choose your project setup: # React frontends only
126
+ > Starter / Standard / Full / Custom
127
+
128
+ ? What is your project name? # Skipped if provided via CLI
129
+ > my-app
69
130
 
70
- ## 🏃‍♂️ Getting Started After Creation
131
+ ? Use TypeScript? # Vite Custom preset only
132
+ > Yes / No
71
133
 
72
- Once your project is created:
134
+ ? Choose a CSS framework: # Frontend frameworks
135
+ > Tailwind CSS / Bootstrap / MUI / ...
136
+
137
+ ? Choose authentication strategy: # Customize mode
138
+ > Clerk / NextAuth.js / Auth0 / ...
139
+
140
+ ? Select Routing features: # React feature categories
141
+ ? Select State Management features:
142
+ ? Select Forms features:
143
+ ...
144
+
145
+ ? After setup, do you want to:
146
+ [ ] Initialize a Git repository
147
+ [ ] Install dependencies
148
+ [ ] Run the development server
149
+ ```
150
+
151
+ ## After Project Creation
73
152
 
74
153
  ```bash
75
- cd your-app-name
76
- npm install # Install dependencies
77
- npm run dev # Start development server
154
+ cd my-app
155
+ npm install # If not done during setup
156
+ npm run dev # Start development server
78
157
  ```
79
158
 
80
- **For Next.js/Vite**: Open http://localhost:3000
81
- **For Express/Fastify**: API runs on http://localhost:3000
159
+ **Default ports:**
82
160
 
83
- ## 🤝 Contributing
161
+ | Framework | Port | URL |
162
+ |-----------|------|-----|
163
+ | Next.js | 3000 | http://localhost:3000 |
164
+ | Vite + React | 5173 | http://localhost:5173 |
165
+ | Rsbuild + React | 3000 | http://localhost:3000 |
166
+ | Express.js | 3000 | http://localhost:3000 |
167
+ | Fastify | 3000 | http://localhost:3000 |
84
168
 
85
- We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
169
+ ## Project Structure Example
86
170
 
87
- ## 📄 License
171
+ A typical **Vite + React** project with the **feature-based** structure:
88
172
 
89
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
173
+ ```
174
+ my-app/
175
+ ├── public/
176
+ ├── src/
177
+ │ ├── main.tsx
178
+ │ ├── App.tsx
179
+ │ ├── index.css
180
+ │ ├── components/
181
+ │ │ └── common/
182
+ │ │ └── Header.tsx
183
+ │ ├── features/
184
+ │ │ ├── auth/
185
+ │ │ │ └── Login.tsx
186
+ │ │ └── dashboard/
187
+ │ │ └── Dashboard.tsx
188
+ │ ├── hooks/
189
+ │ │ └── useLocalStorage.ts
190
+ │ ├── services/
191
+ │ │ └── api.ts
192
+ │ ├── utils/
193
+ │ │ └── helpers.ts
194
+ │ └── types/
195
+ │ └── index.ts
196
+ ├── .env
197
+ ├── .gitignore
198
+ ├── package.json
199
+ ├── vite.config.ts
200
+ └── tsconfig.json
201
+ ```
90
202
 
91
- ---
203
+ ## Why Build App With?
92
204
 
93
- **Why Build App With?**
94
- - **Fast setup** - Get a complete project in under 2 minutes
95
- - 🏗️ **Production-ready** - Best practices and security built-in
96
- - 🎛️ **Flexible** - Choose exactly what you need, nothing you don't
97
- - 📚 **Well-documented** - Clear examples and helpful error messages
98
- - 🔄 **Up-to-date** - Always uses the latest stable versions
205
+ - **Fast** Go from zero to a running project in seconds
206
+ - **Production-ready** Security best practices, proper project structure, and sensible defaults built in
207
+ - **Flexible** 5 frameworks, 4 presets, 30+ configurable features — pick exactly what you need
208
+ - **Secure** Path traversal prevention, command injection guards, and secret generation for backend projects
209
+ - **Current** Uses latest stable versions of all dependencies
99
210
 
211
+ ## Contributing
100
212
 
101
- ## 📞 Support
213
+ Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
102
214
 
103
- - 🐛 Issues: [GitHub Issues](https://github.com/imnayakshubham/build-app-with/issues)
104
- - 💬 Discussions: [GitHub Discussions](https://github.com/imnayakshubham/build-app-with/discussions)
215
+ ## License
105
216
 
106
- **Made with ❤️ by the [Build App With](https://github.com/imnayakshubham/build-app-with)**
217
+ MIT see [LICENSE](LICENSE) for details.
107
218
 
219
+ ## Support
108
220
 
109
- **Start building your next great idea today!** 🚀
221
+ - Issues: [GitHub Issues](https://github.com/imnayakshubham/build-app-with/issues)
222
+ - Discussions: [GitHub Discussions](https://github.com/imnayakshubham/build-app-with/discussions)
@@ -205,7 +205,8 @@ const vite_features_DEPENDENCY_VERSIONS = {
205
205
  'react-i18next': '^14.0.0',
206
206
  'i18next': '^23.7.11',
207
207
  '@storybook/react': '^7.6.3',
208
- '@storybook/react-vite': '^7.6.3'
208
+ '@storybook/react-vite': '^7.6.3',
209
+ 'storybook-builder-rsbuild': '^0.1.1'
209
210
  };
210
211
 
211
212
  // Feature to Package Mapping
@@ -330,7 +331,7 @@ function applyPresetFeatures(answers, preset) {
330
331
  * @returns {boolean} True if feature prompts should be shown
331
332
  */
332
333
  function shouldShowFeaturePrompts(answers) {
333
- return answers.framework === 'vite-react' &&
334
+ return (answers.framework === 'vite-react' || answers.framework === 'rsbuild-react') &&
334
335
  answers.vitePreset &&
335
336
  answers.vitePreset !== VITE_PRESETS.STARTER;
336
337
  }
@@ -369,6 +370,8 @@ function getProperty(answers, property, defaultValue = '') {
369
370
 
370
371
 
371
372
 
373
+ const isReactFrontend = (fw) => fw === 'vite-react' || fw === 'rsbuild-react';
374
+
372
375
  function getFeaturePrompts(currentAnswers) {
373
376
  return [
374
377
  {
@@ -381,7 +384,7 @@ function getFeaturePrompts(currentAnswers) {
381
384
  when: (default_answers) => {
382
385
  const answers = default_answers ?? currentAnswers
383
386
  return (answers.setupType === 'customize' || answers.allowCustomFeatures) &&
384
- answers.framework === 'vite-react'
387
+ isReactFrontend(answers.framework)
385
388
  }
386
389
  },
387
390
  {
@@ -497,6 +500,7 @@ function getInitialPrompts(cliProjectName = null) {
497
500
  choices: [
498
501
  { name: 'Next.js (React Full-stack)', value: 'nextjs' },
499
502
  { name: 'Vite + React (Frontend)', value: 'vite-react' },
503
+ { name: 'Rsbuild + React (Frontend)', value: 'rsbuild-react' },
500
504
  { name: 'Express.js (Node.js Backend)', value: 'express' },
501
505
  { name: 'Fastify (Node.js Backend)', value: 'fastify' }
502
506
  ]
@@ -513,7 +517,7 @@ function getInitialPrompts(cliProjectName = null) {
513
517
  { name: '⚙️ Custom - Advanced (choose features manually)', value: 'custom' }
514
518
  ],
515
519
  default: 1, // Standard is default
516
- when: (answers) => answers.framework === 'vite-react'
520
+ when: (answers) => isReactFrontend(answers.framework)
517
521
  },
518
522
  // 2b. Setup type for other frameworks
519
523
  {
@@ -524,7 +528,7 @@ function getInitialPrompts(cliProjectName = null) {
524
528
  { name: 'Default (Quick start with recommended settings)', value: 'default' },
525
529
  { name: 'Customize (Advanced: choose your own settings)', value: 'customize' }
526
530
  ],
527
- when: (answers) => answers.framework !== 'vite-react'
531
+ when: (answers) => !isReactFrontend(answers.framework)
528
532
  }
529
533
  ];
530
534
 
@@ -584,8 +588,8 @@ function getCustomizationPrompts(initialAnswers) {
584
588
  ],
585
589
  when: (answers) => {
586
590
  const merged = { ...initialAnswers, ...answers };
587
- // For Vite: show for non-Starter presets
588
- if (merged.framework === 'vite-react') {
591
+ // For React frontends (Vite/Rsbuild): show for non-Starter presets
592
+ if (isReactFrontend(merged.framework)) {
589
593
  return merged.vitePreset !== 'starter';
590
594
  }
591
595
  // Only show CSS framework for frontend frameworks
@@ -610,7 +614,7 @@ function getCustomizationPrompts(initialAnswers) {
610
614
  default: 'clerk',
611
615
  when: (answers) => {
612
616
  const merged = { ...initialAnswers, ...answers };
613
- return merged.setupType === 'customize' && ['nextjs', 'vite-react'].includes(merged.framework);
617
+ return merged.setupType === 'customize' && ['nextjs', 'vite-react', 'rsbuild-react'].includes(merged.framework);
614
618
  }
615
619
  },
616
620
  {
@@ -624,7 +628,7 @@ function getCustomizationPrompts(initialAnswers) {
624
628
  ],
625
629
  when: (answers) => {
626
630
  const merged = { ...initialAnswers, ...answers };
627
- return merged.setupType === 'customize' && merged.framework === 'vite-react';
631
+ return merged.setupType === 'customize' && isReactFrontend(merged.framework);
628
632
  }
629
633
  },
630
634
  // ESLint and Prettier prompts hidden per user request
@@ -652,14 +656,14 @@ function getCustomizationPrompts(initialAnswers) {
652
656
  }
653
657
  ];
654
658
 
655
- // Add feature prompts for Vite + React users
659
+ // Add feature prompts for React frontend users (Vite + Rsbuild)
656
660
  const featurePrompts = getFeaturePrompts();
657
661
  featurePrompts.forEach(prompt => {
658
662
  const originalWhen = prompt.when;
659
663
  prompt.when = (answers) => {
660
664
  const merged = { ...initialAnswers, ...answers };
661
- // Show for Vite + React framework
662
- if (merged.framework === 'vite-react') {
665
+ // Show for React frontend frameworks (Vite / Rsbuild)
666
+ if (isReactFrontend(merged.framework)) {
663
667
  // For Custom preset: show all feature prompts
664
668
  if (merged.vitePreset === 'custom') {
665
669
  return originalWhen ? originalWhen(merged) : true;
@@ -701,8 +705,8 @@ function getPrompts(cliProjectName = null) {
701
705
  { name: 'Vanilla CSS', value: 'vanilla' }
702
706
  ],
703
707
  when: (answers) => {
704
- // For Vite: show for non-Starter presets
705
- if (answers.framework === 'vite-react') {
708
+ // For React frontends (Vite/Rsbuild): show for non-Starter presets
709
+ if (isReactFrontend(answers.framework)) {
706
710
  return answers.vitePreset !== 'starter';
707
711
  }
708
712
  // Only show CSS framework for frontend frameworks
@@ -725,7 +729,7 @@ function getPrompts(cliProjectName = null) {
725
729
  { name: 'None', value: 'none' }
726
730
  ],
727
731
  default: 'clerk',
728
- when: (answers) => answers.setupType === 'customize' && ['nextjs', 'vite-react'].includes(answers.framework)
732
+ when: (answers) => answers.setupType === 'customize' && ['nextjs', 'vite-react', 'rsbuild-react'].includes(answers.framework)
729
733
  },
730
734
  {
731
735
  type: 'list',
@@ -736,7 +740,7 @@ function getPrompts(cliProjectName = null) {
736
740
  { name: 'Simple (Basic structure)', value: 'simple' },
737
741
  { name: 'Domain-driven (Advanced architecture)', value: 'domain-driven' }
738
742
  ],
739
- when: (answers) => answers.setupType === 'customize' && answers.framework === 'vite-react'
743
+ when: (answers) => answers.setupType === 'customize' && isReactFrontend(answers.framework)
740
744
  },
741
745
  // ESLint and Prettier prompts hidden per user request
742
746
  {
@@ -768,7 +772,7 @@ function getPrompts(cliProjectName = null) {
768
772
  featurePrompts.forEach(prompt => {
769
773
  const originalWhen = prompt.when;
770
774
  prompt.when = (answers) => {
771
- if (answers.framework === 'vite-react') {
775
+ if (isReactFrontend(answers.framework)) {
772
776
  if (answers.vitePreset === 'custom') {
773
777
  return originalWhen ? originalWhen(answers) : true;
774
778
  }
@@ -793,9 +797,13 @@ function finalizeAnswers(answers) {
793
797
  if (answers.framework === 'vite-react' && answers.typescript === undefined) {
794
798
  answers.typescript = true;
795
799
  }
800
+ // Rsbuild always uses TypeScript
801
+ if (answers.framework === 'rsbuild-react') {
802
+ answers.typescript = true;
803
+ }
796
804
 
797
- // Apply Vite preset features
798
- if (answers.framework === 'vite-react' && answers.vitePreset) {
805
+ // Apply preset features for React frontends (Vite / Rsbuild)
806
+ if (isReactFrontend(answers.framework) && answers.vitePreset) {
799
807
  // Set setupType for compatibility with existing code
800
808
  answers.setupType = presetToSetupType(answers.vitePreset);
801
809
 
@@ -975,7 +983,7 @@ function generatePackageJson(answers) {
975
983
  }
976
984
 
977
985
  // Automatically add TanStack Query and Axios for React-based apps
978
- if (answers.framework === 'vite-react' || answers.framework === 'nextjs') {
986
+ if (answers.framework === 'vite-react' || answers.framework === 'rsbuild-react' || answers.framework === 'nextjs') {
979
987
  packageJson.dependencies['@tanstack/react-query'] = '^5.14.2';
980
988
  packageJson.dependencies['axios'] = '^1.6.2';
981
989
  packageJson.devDependencies['@tanstack/react-query-devtools'] = '^5.14.2';
@@ -1170,14 +1178,14 @@ function getOverlayDependencies(answers) {
1170
1178
  'react-select': ['react-select'],
1171
1179
  'react-helmet': ['react-helmet-async'],
1172
1180
  'react-i18next': ['react-i18next', 'i18next'],
1173
- 'storybook': ['@storybook/react', '@storybook/react-vite']
1181
+ 'storybook': ['@storybook/react', answers.framework === 'rsbuild-react' ? 'storybook-builder-rsbuild' : '@storybook/react-vite']
1174
1182
  };
1175
1183
  answers.utilities.forEach(util => {
1176
1184
  const pkgs = utilPkgMap[util] || [];
1177
1185
  pkgs.forEach(pkg => {
1178
1186
  const version = vite_features_DEPENDENCY_VERSIONS[pkg] || 'latest';
1179
1187
  // Storybook packages are dev dependencies
1180
- if (pkg.startsWith('@storybook/')) {
1188
+ if (pkg.startsWith('@storybook/') || pkg === 'storybook-builder-rsbuild') {
1181
1189
  devDependencies[pkg] = version;
1182
1190
  } else {
1183
1191
  dependencies[pkg] = version;
@@ -4057,7 +4065,9 @@ const ALLOWED_COMMANDS = {
4057
4065
  'create-next-app@latest',
4058
4066
  'create-next-app',
4059
4067
  'create-vite@latest',
4060
- 'create-vite'
4068
+ 'create-vite',
4069
+ 'create-rsbuild@latest',
4070
+ 'create-rsbuild'
4061
4071
  ],
4062
4072
  allowedFlags: [
4063
4073
  '--version', '--help',
@@ -4151,6 +4161,11 @@ function validateCommandArgs(command, args) {
4151
4161
  if (npxCommand.startsWith('create-vite')) {
4152
4162
  return validateCreateViteArgs(args.slice(1), config);
4153
4163
  }
4164
+
4165
+ // For create-rsbuild, validate remaining arguments
4166
+ if (npxCommand.startsWith('create-rsbuild')) {
4167
+ return validateCreateRsbuildArgs(args.slice(1), config);
4168
+ }
4154
4169
  }
4155
4170
 
4156
4171
  for (const arg of args) {
@@ -4272,6 +4287,66 @@ function validateCreateViteArgs(args, config) {
4272
4287
  return true;
4273
4288
  }
4274
4289
 
4290
+ /**
4291
+ * Validate create-rsbuild specific arguments
4292
+ * @param {string[]} args - Arguments after create-rsbuild command
4293
+ * @param {Object} config - NPX command configuration
4294
+ * @returns {boolean} True if arguments are valid
4295
+ */
4296
+ function validateCreateRsbuildArgs(args, config) {
4297
+ const VALID_TEMPLATES = ['react', 'react-ts'];
4298
+
4299
+ for (let i = 0; i < args.length; i++) {
4300
+ const arg = args[i];
4301
+
4302
+ // Check for shell metacharacters
4303
+ if (/[;&|`$(){}[\]<>\\]/.test(arg)) {
4304
+ logger.error(`Argument contains shell metacharacters: ${arg}`);
4305
+ return false;
4306
+ }
4307
+
4308
+ if (arg.startsWith('-')) {
4309
+ // It's a flag
4310
+ if (!config.allowedFlags.includes(arg)) {
4311
+ logger.error(`Flag not in allowlist: ${arg}`);
4312
+ return false;
4313
+ }
4314
+
4315
+ // Handle --template flag with value
4316
+ if (arg === '--template' && i + 1 < args.length) {
4317
+ const template = args[i + 1];
4318
+ if (!VALID_TEMPLATES.includes(template)) {
4319
+ logger.error(`Invalid Rsbuild template: ${template}`);
4320
+ return false;
4321
+ }
4322
+ i++; // Skip the next argument as it's the value for this flag
4323
+ }
4324
+ } else {
4325
+ // It's likely a project name - validate it
4326
+ if (!validatePackageName(arg) && !/^[a-zA-Z0-9-_]+$/.test(arg)) {
4327
+ logger.error(`Invalid project name: ${arg}`);
4328
+ return false;
4329
+ }
4330
+ }
4331
+ }
4332
+
4333
+ return true;
4334
+ }
4335
+
4336
+ /**
4337
+ * Validate and sanitize Rsbuild project creation arguments
4338
+ * @param {string} projectName - Project name
4339
+ * @param {Object} options - Rsbuild options
4340
+ * @returns {string[]} Sanitized arguments array
4341
+ */
4342
+ function sanitizeRsbuildArgs(projectName, options = {}) {
4343
+ const sanitizedName = sanitizeProjectName(projectName);
4344
+ const args = ['create-rsbuild@latest', sanitizedName];
4345
+ const template = options.typescript ? 'react-ts' : 'react';
4346
+ args.push('--template', template);
4347
+ return args;
4348
+ }
4349
+
4275
4350
  /**
4276
4351
  * Validate and sanitize Vite project creation arguments
4277
4352
  * @param {string} projectName - Project name
@@ -4734,6 +4809,347 @@ async function generateCreditsComponent(projectPath, answers) {
4734
4809
  await external_fs_extra_["default"].writeFile(external_path_namespaceObject.join(srcDir, 'components', 'Credits.jsx'), creditsComponent);
4735
4810
  }
4736
4811
 
4812
+ ;// ./src/generators/rsbuild/rsbuild-project-generator.js
4813
+
4814
+
4815
+
4816
+
4817
+
4818
+
4819
+
4820
+
4821
+
4822
+
4823
+
4824
+ /**
4825
+ * Create the base Rsbuild project using create-rsbuild CLI
4826
+ */
4827
+ async function createRsbuildBase(projectPath, answers) {
4828
+ logger.debug('Creating base Rsbuild project...');
4829
+
4830
+ try {
4831
+ const sanitizedArgs = sanitizeRsbuildArgs(answers.projectName, {
4832
+ typescript: true // Rsbuild always uses TypeScript
4833
+ });
4834
+
4835
+ await secureExec('npx', sanitizedArgs, {
4836
+ stdio: 'pipe',
4837
+ cwd: process.cwd(),
4838
+ timeout: 300000 // 5 minutes
4839
+ });
4840
+
4841
+ logger.debug('Base Rsbuild project created');
4842
+ } catch (error) {
4843
+ throw new Error(`Failed to create Rsbuild project: ${error.message}`);
4844
+ }
4845
+ }
4846
+
4847
+ /**
4848
+ * Overlay customizations onto a scaffolded Rsbuild project.
4849
+ * Assumes create-rsbuild has already run and created the base project.
4850
+ * Reuses Vite file generators since the React code is build-tool agnostic.
4851
+ */
4852
+ async function generateRsbuildProject(projectPath, answers) {
4853
+ // Merge overlay dependencies into the scaffold's package.json
4854
+ await rsbuild_project_generator_mergeOverlayDependencies(projectPath, answers);
4855
+
4856
+ // Generate rsbuild.config.ts (always TypeScript)
4857
+ await generateRsbuildConfig(projectPath, answers);
4858
+
4859
+ // Generate Tailwind config if needed
4860
+ if (answers.cssFramework === 'tailwind') {
4861
+ const tailwindConfig = generateTailwindConfig(answers);
4862
+ await external_fs_extra_["default"].writeFile(external_path_namespaceObject.join(projectPath, 'tailwind.config.js'), tailwindConfig);
4863
+ }
4864
+
4865
+ // Generate ESLint config if requested
4866
+ if (answers.eslint) {
4867
+ const eslintConfig = generateESLintConfig(answers);
4868
+ await external_fs_extra_["default"].writeJSON(external_path_namespaceObject.join(projectPath, '.eslintrc.json'), eslintConfig, { spaces: 2 });
4869
+ }
4870
+
4871
+ // Generate Prettier config if requested
4872
+ if (answers.prettier) {
4873
+ const prettierConfig = generatePrettierConfig();
4874
+ await external_fs_extra_["default"].writeJSON(external_path_namespaceObject.join(projectPath, '.prettierrc'), prettierConfig, { spaces: 2 });
4875
+ }
4876
+
4877
+ // Remove default create-rsbuild assets we'll replace
4878
+ await rsbuild_project_generator_removeDefaultAssets(projectPath);
4879
+
4880
+ // Generate project files (overlay mode - reuses Vite file generators)
4881
+ await generateProjectFiles(projectPath, answers, { overlay: true });
4882
+
4883
+ // Generate Clerk authentication if selected
4884
+ if (answers.authStrategy === 'clerk') {
4885
+ await rsbuild_project_generator_generateClerkAuth(projectPath, answers);
4886
+ }
4887
+
4888
+ // Generate .env.example (Rsbuild uses PUBLIC_ prefix)
4889
+ await rsbuild_project_generator_generateEnvExample(projectPath, answers);
4890
+
4891
+ // Generate README (Rsbuild-specific)
4892
+ await rsbuild_project_generator_generateReadme(projectPath, answers);
4893
+
4894
+ // Generate credits component
4895
+ await rsbuild_project_generator_generateCreditsComponent(projectPath, answers);
4896
+ }
4897
+
4898
+ /**
4899
+ * Read the scaffold's package.json and merge in overlay dependencies
4900
+ */
4901
+ async function rsbuild_project_generator_mergeOverlayDependencies(projectPath, answers) {
4902
+ const pkgPath = external_path_namespaceObject.join(projectPath, 'package.json');
4903
+ const existingPkg = await external_fs_extra_["default"].readJSON(pkgPath);
4904
+ const overlay = getOverlayDependencies(answers);
4905
+
4906
+ existingPkg.dependencies = {
4907
+ ...(existingPkg.dependencies || {}),
4908
+ ...overlay.dependencies
4909
+ };
4910
+
4911
+ existingPkg.devDependencies = {
4912
+ ...(existingPkg.devDependencies || {}),
4913
+ ...overlay.devDependencies
4914
+ };
4915
+
4916
+ await external_fs_extra_["default"].writeJSON(pkgPath, existingPkg, { spaces: 2 });
4917
+ }
4918
+
4919
+ /**
4920
+ * Generate rsbuild.config.ts with project settings
4921
+ */
4922
+ async function generateRsbuildConfig(projectPath, answers) {
4923
+ const config = `import { defineConfig } from '@rsbuild/core';
4924
+ import { pluginReact } from '@rsbuild/plugin-react';
4925
+
4926
+ export default defineConfig({
4927
+ plugins: [pluginReact()],
4928
+ source: {
4929
+ entry: {
4930
+ index: './src/main.tsx'
4931
+ }
4932
+ },
4933
+ server: {
4934
+ port: 3000,
4935
+ open: true
4936
+ },
4937
+ output: {
4938
+ sourceMap: true
4939
+ }
4940
+ });
4941
+ `;
4942
+
4943
+ await external_fs_extra_["default"].writeFile(external_path_namespaceObject.join(projectPath, 'rsbuild.config.ts'), config);
4944
+ }
4945
+
4946
+ /**
4947
+ * Remove default create-rsbuild assets that we'll replace with our own
4948
+ */
4949
+ async function rsbuild_project_generator_removeDefaultAssets(projectPath) {
4950
+ const filesToRemove = [
4951
+ external_path_namespaceObject.join(projectPath, 'src', 'index.tsx'),
4952
+ external_path_namespaceObject.join(projectPath, 'src', 'App.css'),
4953
+ external_path_namespaceObject.join(projectPath, 'src', 'App.tsx')
4954
+ ];
4955
+
4956
+ for (const filePath of filesToRemove) {
4957
+ if (await external_fs_extra_["default"].pathExists(filePath)) {
4958
+ await external_fs_extra_["default"].remove(filePath);
4959
+ }
4960
+ }
4961
+ }
4962
+
4963
+ async function rsbuild_project_generator_generateReadme(projectPath, answers) {
4964
+ const selectedFeatures = Array.isArray(answers.features) ? answers.features : [];
4965
+ const allFeatures = [...selectedFeatures];
4966
+ if (answers.authStrategy && answers.authStrategy !== 'none') {
4967
+ allFeatures.push(answers.authStrategy);
4968
+ }
4969
+ if (answers.typescript) {
4970
+ allFeatures.push('typescript');
4971
+ }
4972
+
4973
+ const credits = generateCreditsSection('rsbuild-react', allFeatures);
4974
+
4975
+ const readme = `# ${answers.projectName}
4976
+
4977
+ A React application built with Rsbuild and ${answers.cssFramework === 'vanilla' ? 'vanilla CSS' : answers.cssFramework}.
4978
+
4979
+ ## Features
4980
+
4981
+ - \u26A1\uFE0F Rsbuild (Rspack) for fast development and building
4982
+ - \u269B\uFE0F React + TypeScript
4983
+ - \uD83C\uDFA8 ${answers.cssFramework === 'vanilla' ? 'Vanilla CSS' : answers.cssFramework}
4984
+ ${answers.authStrategy && answers.authStrategy !== 'none' ? `- \uD83D\uDD10 ${answers.authStrategy === 'clerk' ? 'Clerk Authentication' : answers.authStrategy}` : ''}
4985
+ ${selectedFeatures.length > 0 ? `- \uD83D\uDCE6 Additional packages: ${selectedFeatures.join(', ')}` : ''}
4986
+
4987
+ ## Getting Started
4988
+
4989
+ 1. Install dependencies:
4990
+ \`\`\`bash
4991
+ npm install
4992
+ \`\`\`
4993
+
4994
+ 2. Start the development server:
4995
+ \`\`\`bash
4996
+ npm run dev
4997
+ \`\`\`
4998
+
4999
+ 3. Build for production:
5000
+ \`\`\`bash
5001
+ npm run build
5002
+ \`\`\`
5003
+
5004
+ ## Project Structure
5005
+
5006
+ This project uses a ${answers.projectStructure || 'simple'} structure for better organization and maintainability.
5007
+
5008
+ ## Available Scripts
5009
+
5010
+ - \`npm run dev\` - Start development server
5011
+ - \`npm run build\` - Build for production
5012
+ - \`npm run preview\` - Preview production build
5013
+
5014
+ ${answers.authStrategy === 'clerk' ? `
5015
+ ## \uD83D\uDD10 Clerk Authentication Setup
5016
+
5017
+ 1. Sign up at [clerk.com](https://clerk.com)
5018
+ 2. Create a new application
5019
+ 3. Copy your API keys to \`.env.local\`:
5020
+ \`\`\`bash
5021
+ PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXX
5022
+ \`\`\`
5023
+
5024
+ \u26A0\uFE0F **Security Warning**: Never commit real API keys to version control!
5025
+ 4. Configure your authentication settings in the Clerk dashboard
5026
+ ` : ''}
5027
+
5028
+ ## License
5029
+
5030
+ MIT
5031
+
5032
+ ${credits}
5033
+ `;
5034
+
5035
+ await external_fs_extra_["default"].writeFile(external_path_namespaceObject.join(projectPath, 'README.md'), readme);
5036
+ }
5037
+
5038
+ async function rsbuild_project_generator_generateClerkAuth(projectPath, answers) {
5039
+ const srcDir = external_path_namespaceObject.join(projectPath, 'src');
5040
+ await external_fs_extra_["default"].ensureDir(srcDir);
5041
+
5042
+ // Generate Clerk provider (uses PUBLIC_ prefix for Rsbuild)
5043
+ const clerkProvider = `import React from 'react';
5044
+ import { ClerkProvider } from '@clerk/clerk-react';
5045
+
5046
+ const CLERK_PUBLISHABLE_KEY = import.meta.env.PUBLIC_CLERK_PUBLISHABLE_KEY;
5047
+
5048
+ if (!CLERK_PUBLISHABLE_KEY) {
5049
+ throw new Error('Missing Publishable Key');
5050
+ }
5051
+
5052
+ export function ClerkProviderWrapper({ children }) {
5053
+ return (
5054
+ <ClerkProvider publishableKey={CLERK_PUBLISHABLE_KEY}>
5055
+ {children}
5056
+ </ClerkProvider>
5057
+ );
5058
+ }
5059
+ `;
5060
+
5061
+ await external_fs_extra_["default"].ensureDir(external_path_namespaceObject.join(srcDir, 'components'));
5062
+ await external_fs_extra_["default"].writeFile(external_path_namespaceObject.join(srcDir, 'components', 'ClerkProvider.jsx'), clerkProvider);
5063
+
5064
+ // Generate auth components
5065
+ const signInComponent = `import React from 'react';
5066
+ import { SignIn } from '@clerk/clerk-react';
5067
+
5068
+ export function SignInForm() {
5069
+ return (
5070
+ <div className="flex items-center justify-center min-h-screen">
5071
+ <SignIn />
5072
+ </div>
5073
+ );
5074
+ }
5075
+ `;
5076
+
5077
+ const signUpComponent = `import React from 'react';
5078
+ import { SignUp } from '@clerk/clerk-react';
5079
+
5080
+ export function SignUpForm() {
5081
+ return (
5082
+ <div className="flex items-center justify-center min-h-screen">
5083
+ <SignUp />
5084
+ </div>
5085
+ );
5086
+ }
5087
+ `;
5088
+
5089
+ const userButtonComponent = `import React from 'react';
5090
+ import { UserButton } from '@clerk/clerk-react';
5091
+
5092
+ export function UserProfile() {
5093
+ return (
5094
+ <div className="flex items-center gap-4">
5095
+ <UserButton afterSignOutUrl="/" />
5096
+ </div>
5097
+ );
5098
+ }
5099
+ `;
5100
+
5101
+ await external_fs_extra_["default"].writeFile(external_path_namespaceObject.join(srcDir, 'components', 'SignInForm.jsx'), signInComponent);
5102
+ await external_fs_extra_["default"].writeFile(external_path_namespaceObject.join(srcDir, 'components', 'SignUpForm.jsx'), signUpComponent);
5103
+ await external_fs_extra_["default"].writeFile(external_path_namespaceObject.join(srcDir, 'components', 'UserProfile.jsx'), userButtonComponent);
5104
+ }
5105
+
5106
+ async function rsbuild_project_generator_generateEnvExample(projectPath, answers) {
5107
+ let envContent = `# API Configuration
5108
+ PUBLIC_API_URL=http://localhost:3000/api
5109
+ `;
5110
+
5111
+ if (answers.authStrategy === 'clerk') {
5112
+ envContent += `
5113
+ # Clerk Authentication
5114
+ # WARNING: Replace with your actual Clerk API keys from https://clerk.com
5115
+ # Never commit real API keys to version control!
5116
+ PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXX
5117
+ `;
5118
+ } else if (answers.authStrategy === 'auth0') {
5119
+ envContent += `
5120
+ # Auth0 Configuration
5121
+ PUBLIC_AUTH0_DOMAIN=your-tenant.auth0.com
5122
+ PUBLIC_AUTH0_CLIENT_ID=your-client-id
5123
+ `;
5124
+ } else if (answers.authStrategy === 'firebase-auth') {
5125
+ envContent += `
5126
+ # Firebase Configuration
5127
+ PUBLIC_FIREBASE_API_KEY=your-api-key
5128
+ PUBLIC_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
5129
+ PUBLIC_FIREBASE_PROJECT_ID=your-project-id
5130
+ `;
5131
+ }
5132
+
5133
+ await external_fs_extra_["default"].writeFile(external_path_namespaceObject.join(projectPath, '.env.example'), envContent);
5134
+ }
5135
+
5136
+ async function rsbuild_project_generator_generateCreditsComponent(projectPath, answers) {
5137
+ const srcDir = external_path_namespaceObject.join(projectPath, 'src');
5138
+ await external_fs_extra_["default"].ensureDir(external_path_namespaceObject.join(srcDir, 'components'));
5139
+
5140
+ const selectedFeatures = Array.isArray(answers.features) ? answers.features : [];
5141
+ const allFeatures = [...selectedFeatures];
5142
+ if (answers.authStrategy && answers.authStrategy !== 'none') {
5143
+ allFeatures.push(answers.authStrategy);
5144
+ }
5145
+ if (answers.typescript) {
5146
+ allFeatures.push('typescript');
5147
+ }
5148
+
5149
+ const creditsComponent = generateReactCreditsComponent('rsbuild-react', allFeatures);
5150
+ await external_fs_extra_["default"].writeFile(external_path_namespaceObject.join(srcDir, 'components', 'Credits.jsx'), creditsComponent);
5151
+ }
5152
+
4737
5153
  ;// ./src/utils/path-security.js
4738
5154
  /**
4739
5155
  * Path security utilities to prevent directory traversal and path injection attacks
@@ -9470,6 +9886,7 @@ function successMessage(projectName) {
9470
9886
 
9471
9887
 
9472
9888
 
9889
+
9473
9890
  async function createApp(cliProjectName = null) {
9474
9891
  try {
9475
9892
  // === Stage 1: Initial prompts (framework, preset, project name, TypeScript) ===
@@ -9487,6 +9904,7 @@ async function createApp(cliProjectName = null) {
9487
9904
 
9488
9905
  const isNext = initialAnswers.framework === 'nextjs';
9489
9906
  const isVite = initialAnswers.framework === 'vite-react';
9907
+ const isRsbuild = initialAnswers.framework === 'rsbuild-react';
9490
9908
 
9491
9909
  // === Stage 2: Run official scaffold for frameworks that use CLI tools ===
9492
9910
  if (isVite) {
@@ -9503,6 +9921,19 @@ async function createApp(cliProjectName = null) {
9503
9921
  logger.stopSpinner(false, 'Failed to create Vite project');
9504
9922
  throw error;
9505
9923
  }
9924
+ } else if (isRsbuild) {
9925
+ if (initialAnswers.typescript === undefined) {
9926
+ initialAnswers.typescript = true;
9927
+ }
9928
+
9929
+ const spinner = logger.startSpinner('Creating Rsbuild project...');
9930
+ try {
9931
+ await createRsbuildBase(projectPath, initialAnswers);
9932
+ logger.stopSpinner(true, 'Rsbuild project scaffold created!');
9933
+ } catch (error) {
9934
+ logger.stopSpinner(false, 'Failed to create Rsbuild project');
9935
+ throw error;
9936
+ }
9506
9937
  } else if (!isNext) {
9507
9938
  // For Express/Fastify, create directory ourselves
9508
9939
  logger.debug(`Creating project directory: ${projectPath}`);
@@ -9512,7 +9943,7 @@ async function createApp(cliProjectName = null) {
9512
9943
  // Next.js: directory creation is handled by create-next-app inside generateNextJSProject
9513
9944
 
9514
9945
  // Derive setupType from vitePreset so customization prompts can gate on it
9515
- if (isVite && initialAnswers.vitePreset) {
9946
+ if ((isVite || isRsbuild) && initialAnswers.vitePreset) {
9516
9947
  initialAnswers.setupType = presetToSetupType(initialAnswers.vitePreset);
9517
9948
  }
9518
9949
 
@@ -9534,6 +9965,15 @@ async function createApp(cliProjectName = null) {
9534
9965
  logger.stopSpinner(false, 'Failed to customize project');
9535
9966
  throw error;
9536
9967
  }
9968
+ } else if (isRsbuild) {
9969
+ const spinner = logger.startSpinner('Customizing project...');
9970
+ try {
9971
+ await generateRsbuildProject(projectPath, answers);
9972
+ logger.stopSpinner(true, 'Project customized successfully!');
9973
+ } catch (error) {
9974
+ logger.stopSpinner(false, 'Failed to customize project');
9975
+ throw error;
9976
+ }
9537
9977
  } else if (isNext) {
9538
9978
  await generateNextJSProject(projectPath, answers);
9539
9979
  } else if (answers.framework === 'express') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "build-app-with",
3
- "version": "2.0.11",
3
+ "version": "2.0.12",
4
4
  "description": "🚀 Interactive CLI tool to quickly create modern web applications. Use with 'npx build-app-with my-app' or install globally. Choose from React (Next.js/Vite), Node.js backends (Express/Fastify), with built-in TypeScript, authentication, databases, and more.",
5
5
  "main": "index.js",
6
6
  "type": "module",