@beknurakhmed/webforge-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +212 -0
- package/dist/index.js +386 -0
- package/package.json +63 -0
- package/templates/angular/angular.json +36 -0
- package/templates/angular/package.json +29 -0
- package/templates/angular/src/app/app.component.ts +27 -0
- package/templates/angular/src/index.html +11 -0
- package/templates/angular/src/main.ts +4 -0
- package/templates/angular/src/styles.css +24 -0
- package/templates/angular/tsconfig.json +27 -0
- package/templates/base/README.md +16 -0
- package/templates/base/_gitignore +11 -0
- package/templates/extras/eslint/config/eslint.config.js +18 -0
- package/templates/extras/eslint/deps.json +7 -0
- package/templates/extras/prettier/config/.prettierignore +6 -0
- package/templates/extras/prettier/config/.prettierrc +7 -0
- package/templates/extras/prettier/deps.json +5 -0
- package/templates/nextjs/next.config.ts +5 -0
- package/templates/nextjs/package.json +16 -0
- package/templates/nextjs/src/app/globals.css +36 -0
- package/templates/nextjs/src/app/layout.tsx +19 -0
- package/templates/nextjs/src/app/page.tsx +8 -0
- package/templates/nextjs/tsconfig.json +21 -0
- package/templates/nuxt/app.vue +3 -0
- package/templates/nuxt/assets/css/main.css +24 -0
- package/templates/nuxt/nuxt.config.ts +5 -0
- package/templates/nuxt/package.json +17 -0
- package/templates/nuxt/pages/index.vue +20 -0
- package/templates/nuxt/tsconfig.json +3 -0
- package/templates/overlays/blog/react/src/App.css +41 -0
- package/templates/overlays/blog/react/src/App.tsx +39 -0
- package/templates/overlays/blog/react/src/components/BlogFooter.tsx +7 -0
- package/templates/overlays/blog/react/src/components/BlogHeader.tsx +15 -0
- package/templates/overlays/blog/react/src/components/BlogSidebar.tsx +26 -0
- package/templates/overlays/blog/react/src/components/PostList.tsx +27 -0
- package/templates/overlays/crm/react/src/App.css +48 -0
- package/templates/overlays/crm/react/src/App.tsx +49 -0
- package/templates/overlays/crm/react/src/components/ContactsTable.tsx +40 -0
- package/templates/overlays/crm/react/src/components/Filters.tsx +22 -0
- package/templates/overlays/crm/react/src/components/Sidebar.tsx +25 -0
- package/templates/overlays/crm/react/src/components/StatsCards.tsx +26 -0
- package/templates/overlays/dashboard/react/src/App.css +64 -0
- package/templates/overlays/dashboard/react/src/App.tsx +30 -0
- package/templates/overlays/dashboard/react/src/components/ChartPlaceholder.tsx +18 -0
- package/templates/overlays/dashboard/react/src/components/DataTable.tsx +44 -0
- package/templates/overlays/dashboard/react/src/components/KPICards.tsx +22 -0
- package/templates/overlays/dashboard/react/src/components/Sidebar.tsx +24 -0
- package/templates/overlays/ecommerce/react/src/App.css +82 -0
- package/templates/overlays/ecommerce/react/src/App.tsx +68 -0
- package/templates/overlays/ecommerce/react/src/components/Cart.tsx +47 -0
- package/templates/overlays/ecommerce/react/src/components/Footer.tsx +29 -0
- package/templates/overlays/ecommerce/react/src/components/Header.tsx +26 -0
- package/templates/overlays/ecommerce/react/src/components/ProductGrid.tsx +32 -0
- package/templates/overlays/ecommerce/vue/src/App.vue +44 -0
- package/templates/overlays/ecommerce/vue/src/components/CartPanel.vue +46 -0
- package/templates/overlays/ecommerce/vue/src/components/ProductGrid.vue +40 -0
- package/templates/overlays/ecommerce/vue/src/components/StoreFooter.vue +17 -0
- package/templates/overlays/ecommerce/vue/src/components/StoreHeader.vue +33 -0
- package/templates/overlays/landing/angular/src/app/app.component.ts +21 -0
- package/templates/overlays/landing/angular/src/app/components/cta.component.ts +24 -0
- package/templates/overlays/landing/angular/src/app/components/features.component.ts +42 -0
- package/templates/overlays/landing/angular/src/app/components/footer.component.ts +45 -0
- package/templates/overlays/landing/angular/src/app/components/hero.component.ts +41 -0
- package/templates/overlays/landing/nextjs/src/app/components/CTA.tsx +12 -0
- package/templates/overlays/landing/nextjs/src/app/components/Features.tsx +26 -0
- package/templates/overlays/landing/nextjs/src/app/components/Footer.tsx +27 -0
- package/templates/overlays/landing/nextjs/src/app/components/Hero.tsx +22 -0
- package/templates/overlays/landing/nextjs/src/app/globals.css +49 -0
- package/templates/overlays/landing/nextjs/src/app/page.tsx +15 -0
- package/templates/overlays/landing/nuxt/components/LandingCta.vue +18 -0
- package/templates/overlays/landing/nuxt/components/LandingFeatures.vue +36 -0
- package/templates/overlays/landing/nuxt/components/LandingFooter.vue +29 -0
- package/templates/overlays/landing/nuxt/components/LandingHero.vue +35 -0
- package/templates/overlays/landing/nuxt/pages/index.vue +15 -0
- package/templates/overlays/landing/react/src/App.css +70 -0
- package/templates/overlays/landing/react/src/App.tsx +18 -0
- package/templates/overlays/landing/react/src/components/CTA.tsx +14 -0
- package/templates/overlays/landing/react/src/components/Features.tsx +50 -0
- package/templates/overlays/landing/react/src/components/Footer.tsx +34 -0
- package/templates/overlays/landing/react/src/components/Hero.tsx +22 -0
- package/templates/overlays/landing/vanilla/src/main.ts +68 -0
- package/templates/overlays/landing/vanilla/src/style.css +43 -0
- package/templates/overlays/landing/vue/src/App.vue +19 -0
- package/templates/overlays/landing/vue/src/components/AppFooter.vue +44 -0
- package/templates/overlays/landing/vue/src/components/CTA.vue +21 -0
- package/templates/overlays/landing/vue/src/components/Features.vue +36 -0
- package/templates/overlays/landing/vue/src/components/Hero.vue +35 -0
- package/templates/overlays/portfolio/react/src/App.css +81 -0
- package/templates/overlays/portfolio/react/src/App.tsx +20 -0
- package/templates/overlays/portfolio/react/src/components/ContactForm.tsx +29 -0
- package/templates/overlays/portfolio/react/src/components/HeroSection.tsx +24 -0
- package/templates/overlays/portfolio/react/src/components/PortfolioFooter.tsx +14 -0
- package/templates/overlays/portfolio/react/src/components/Projects.tsx +33 -0
- package/templates/overlays/portfolio/react/src/components/Skills.tsx +27 -0
- package/templates/react/index.html +13 -0
- package/templates/react/package.json +19 -0
- package/templates/react/public/vite.svg +1 -0
- package/templates/react/src/App.css +11 -0
- package/templates/react/src/App.tsx +12 -0
- package/templates/react/src/index.css +42 -0
- package/templates/react/src/main.tsx +10 -0
- package/templates/react/vite.config.ts +6 -0
- package/templates/state/mobx/deps.json +6 -0
- package/templates/state/mobx/react/src/store/AppStore.ts +15 -0
- package/templates/state/ngrx/angular/src/app/store/app.state.ts +22 -0
- package/templates/state/ngrx/deps.json +7 -0
- package/templates/state/pinia/deps.json +5 -0
- package/templates/state/pinia/vue/src/store/useAppStore.ts +15 -0
- package/templates/state/redux/deps.json +6 -0
- package/templates/state/redux/react/src/store/counterSlice.ts +20 -0
- package/templates/state/redux/react/src/store/index.ts +11 -0
- package/templates/state/rxjs/angular/src/app/services/app-state.service.ts +18 -0
- package/templates/state/rxjs/deps.json +3 -0
- package/templates/state/zustand/deps.json +5 -0
- package/templates/state/zustand/react/src/store/useStore.ts +15 -0
- package/templates/styling/angular-material/deps.json +6 -0
- package/templates/styling/ant-design/deps.json +5 -0
- package/templates/styling/chakra-ui/deps.json +5 -0
- package/templates/styling/css-modules/deps.json +3 -0
- package/templates/styling/material-ui/deps.json +7 -0
- package/templates/styling/scss/deps.json +5 -0
- package/templates/styling/styled-components/deps.json +5 -0
- package/templates/styling/tailwind/config/postcss.config.js +5 -0
- package/templates/styling/tailwind/config/tailwind.config.js +11 -0
- package/templates/styling/tailwind/deps.json +6 -0
- package/templates/vanilla/index.html +13 -0
- package/templates/vanilla/package.json +14 -0
- package/templates/vanilla/src/main.ts +9 -0
- package/templates/vanilla/src/style.css +36 -0
- package/templates/vanilla/vite.config.ts +3 -0
- package/templates/vue/index.html +13 -0
- package/templates/vue/package.json +18 -0
- package/templates/vue/src/App.vue +23 -0
- package/templates/vue/src/main.ts +5 -0
- package/templates/vue/src/style.css +39 -0
- package/templates/vue/vite.config.ts +6 -0
package/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# webforge-cli
|
|
2
|
+
|
|
3
|
+
Interactive CLI to generate production-ready website project templates. Pick your template, framework, styling, state management, and coding paradigm — get a fully configured project in seconds.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx webforge-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or install globally:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g webforge-cli
|
|
15
|
+
webforge-cli
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
You can also pass the project name directly:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx webforge-cli my-app
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## How It Works
|
|
25
|
+
|
|
26
|
+
The CLI walks you through an interactive wizard:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
1. Project name → my-awesome-app
|
|
30
|
+
2. Template type → Landing Page / E-commerce / CRM / Dashboard / Blog / Portfolio
|
|
31
|
+
3. Framework → React / Vue / Angular / Vanilla / Next.js / Nuxt
|
|
32
|
+
4. Coding paradigm → Functional / SOLID OOP
|
|
33
|
+
5. Styling solution → Tailwind CSS / SCSS / CSS Modules / Material UI / ...
|
|
34
|
+
6. State management → Redux / Zustand / MobX / Pinia / NgRx / RxJS / None
|
|
35
|
+
7. Extra tools → TypeScript / ESLint / Prettier
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
After you answer, webforge generates a ready-to-run project:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
cd my-awesome-app
|
|
42
|
+
npm install
|
|
43
|
+
npm run dev
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Template Types
|
|
47
|
+
|
|
48
|
+
| Template | Description |
|
|
49
|
+
|----------|-------------|
|
|
50
|
+
| **Landing Page** | Marketing page with hero, features, CTA, footer |
|
|
51
|
+
| **E-commerce** | Product grid, shopping cart, product cards |
|
|
52
|
+
| **CRM** | Contacts table, stats cards, filters, sidebar |
|
|
53
|
+
| **Dashboard** | KPI cards, charts, data tables, sidebar navigation |
|
|
54
|
+
| **Blog** | Post list, categories sidebar, newsletter signup |
|
|
55
|
+
| **Portfolio** | Hero intro, project grid, skills, contact form |
|
|
56
|
+
|
|
57
|
+
## Frameworks
|
|
58
|
+
|
|
59
|
+
| Framework | Build Tool | Version |
|
|
60
|
+
|-----------|-----------|---------|
|
|
61
|
+
| **React** | Vite | React 19 |
|
|
62
|
+
| **Vue** | Vite | Vue 3.5 |
|
|
63
|
+
| **Angular** | Angular CLI | Angular 19 |
|
|
64
|
+
| **Vanilla** | Vite | Plain TS/JS |
|
|
65
|
+
| **Next.js** | Next.js | v15 |
|
|
66
|
+
| **Nuxt** | Nuxt | v3 |
|
|
67
|
+
|
|
68
|
+
## Styling Options
|
|
69
|
+
|
|
70
|
+
| Styling | Works with |
|
|
71
|
+
|---------|-----------|
|
|
72
|
+
| **Tailwind CSS** | React, Vue, Angular, Vanilla, Next.js, Nuxt |
|
|
73
|
+
| **SCSS** | React, Vue, Angular, Vanilla, Next.js, Nuxt |
|
|
74
|
+
| **CSS Modules** | React, Vue, Vanilla, Next.js, Nuxt |
|
|
75
|
+
| **Styled Components** | React, Next.js |
|
|
76
|
+
| **Material UI** | React, Next.js |
|
|
77
|
+
| **Chakra UI** | React, Next.js |
|
|
78
|
+
| **Ant Design** | React, Vue, Next.js |
|
|
79
|
+
| **Angular Material** | Angular |
|
|
80
|
+
|
|
81
|
+
## State Management
|
|
82
|
+
|
|
83
|
+
| Library | Works with |
|
|
84
|
+
|---------|-----------|
|
|
85
|
+
| **Redux Toolkit** | React, Next.js |
|
|
86
|
+
| **Zustand** | React, Next.js |
|
|
87
|
+
| **MobX** | React, Next.js |
|
|
88
|
+
| **Pinia** | Vue, Nuxt |
|
|
89
|
+
| **RxJS** | Angular |
|
|
90
|
+
| **NgRx** | Angular |
|
|
91
|
+
|
|
92
|
+
## Coding Paradigm
|
|
93
|
+
|
|
94
|
+
Choose between two coding styles for your generated project:
|
|
95
|
+
|
|
96
|
+
- **Functional** — React hooks, Vue composables, Angular signals, pure functions
|
|
97
|
+
- **SOLID OOP** — Class components, services, dependency injection, OOP patterns
|
|
98
|
+
|
|
99
|
+
## Examples
|
|
100
|
+
|
|
101
|
+
### React + Landing Page + Tailwind
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npx webforge-cli
|
|
105
|
+
# → my-landing
|
|
106
|
+
# → Landing Page
|
|
107
|
+
# → React
|
|
108
|
+
# → Functional
|
|
109
|
+
# → Tailwind CSS
|
|
110
|
+
# → None
|
|
111
|
+
# → TypeScript, ESLint, Prettier
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Vue + E-commerce + SCSS + Pinia
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npx webforge-cli
|
|
118
|
+
# → my-store
|
|
119
|
+
# → E-commerce
|
|
120
|
+
# → Vue
|
|
121
|
+
# → Functional
|
|
122
|
+
# → SCSS
|
|
123
|
+
# → Pinia
|
|
124
|
+
# → TypeScript
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Angular + Dashboard + Angular Material + NgRx
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx webforge-cli
|
|
131
|
+
# → admin-panel
|
|
132
|
+
# → Dashboard
|
|
133
|
+
# → Angular
|
|
134
|
+
# → SOLID OOP
|
|
135
|
+
# → Angular Material
|
|
136
|
+
# → NgRx
|
|
137
|
+
# → TypeScript, ESLint
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Project Structure (Generated)
|
|
141
|
+
|
|
142
|
+
Example for React + Landing Page:
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
my-landing/
|
|
146
|
+
├── public/
|
|
147
|
+
│ └── vite.svg
|
|
148
|
+
├── src/
|
|
149
|
+
│ ├── components/
|
|
150
|
+
│ │ ├── Hero.tsx
|
|
151
|
+
│ │ ├── Features.tsx
|
|
152
|
+
│ │ ├── CTA.tsx
|
|
153
|
+
│ │ └── Footer.tsx
|
|
154
|
+
│ ├── App.tsx
|
|
155
|
+
│ ├── App.css
|
|
156
|
+
│ ├── main.tsx
|
|
157
|
+
│ └── index.css
|
|
158
|
+
├── index.html
|
|
159
|
+
├── package.json
|
|
160
|
+
├── vite.config.ts
|
|
161
|
+
├── tailwind.config.js (if Tailwind selected)
|
|
162
|
+
├── eslint.config.js (if ESLint selected)
|
|
163
|
+
├── .prettierrc (if Prettier selected)
|
|
164
|
+
├── .gitignore
|
|
165
|
+
└── README.md
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Architecture
|
|
169
|
+
|
|
170
|
+
webforge-cli uses a **layered overlay system** to generate projects:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
Layer 1: Framework base (React/Vue/Angular/Vanilla/Next.js/Nuxt)
|
|
174
|
+
Layer 2: Template overlay (Landing/E-commerce/CRM/Dashboard/Blog/Portfolio)
|
|
175
|
+
Layer 3: Paradigm variant (Functional/OOP)
|
|
176
|
+
Layer 4: Styling config (Tailwind/SCSS/Material UI/...)
|
|
177
|
+
Layer 5: State management (Redux/Zustand/Pinia/...)
|
|
178
|
+
Layer 6: Extras (ESLint/Prettier)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Each layer copies files on top of the previous one. Dependencies are merged into `package.json` automatically.
|
|
182
|
+
|
|
183
|
+
## Requirements
|
|
184
|
+
|
|
185
|
+
- Node.js >= 18.0.0
|
|
186
|
+
|
|
187
|
+
## Development
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
git clone https://github.com/beknurakhmed/webforge-cli.git
|
|
191
|
+
cd webforge-cli
|
|
192
|
+
npm install
|
|
193
|
+
npm run build
|
|
194
|
+
node dist/index.js
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Scripts
|
|
198
|
+
|
|
199
|
+
| Script | Description |
|
|
200
|
+
|--------|-------------|
|
|
201
|
+
| `npm run build` | Build CLI with tsup |
|
|
202
|
+
| `npm run dev` | Watch mode build |
|
|
203
|
+
| `npm run start` | Run the CLI |
|
|
204
|
+
| `npm run typecheck` | TypeScript type checking |
|
|
205
|
+
|
|
206
|
+
## Contributing
|
|
207
|
+
|
|
208
|
+
Contributions are welcome! Feel free to open issues or submit pull requests at [github.com/beknurakhmed/webforge-cli](https://github.com/beknurakhmed/webforge-cli).
|
|
209
|
+
|
|
210
|
+
## License
|
|
211
|
+
|
|
212
|
+
MIT - [Beknur](https://github.com/beknurakhmed)
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import * as p from "@clack/prompts";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
import path2 from "path";
|
|
7
|
+
import fs3 from "fs";
|
|
8
|
+
|
|
9
|
+
// src/constants.ts
|
|
10
|
+
var TEMPLATE_OPTIONS = [
|
|
11
|
+
{ value: "landing", label: "Landing Page", hint: "Marketing / product landing page" },
|
|
12
|
+
{ value: "ecommerce", label: "E-commerce", hint: "Online store with product listings" },
|
|
13
|
+
{ value: "crm", label: "CRM", hint: "Customer relationship management" },
|
|
14
|
+
{ value: "dashboard", label: "Dashboard", hint: "Admin dashboard with charts & tables" },
|
|
15
|
+
{ value: "blog", label: "Blog", hint: "Content-focused blog layout" },
|
|
16
|
+
{ value: "portfolio", label: "Portfolio", hint: "Personal / professional portfolio" }
|
|
17
|
+
];
|
|
18
|
+
var FRAMEWORK_OPTIONS = [
|
|
19
|
+
{ value: "react", label: "React", hint: "Vite + React 19" },
|
|
20
|
+
{ value: "vue", label: "Vue", hint: "Vite + Vue 3" },
|
|
21
|
+
{ value: "angular", label: "Angular", hint: "Angular 19" },
|
|
22
|
+
{ value: "vanilla", label: "Vanilla", hint: "Vite + plain TS/JS" },
|
|
23
|
+
{ value: "nextjs", label: "Next.js", hint: "Next.js 15 (React)" },
|
|
24
|
+
{ value: "nuxt", label: "Nuxt", hint: "Nuxt 3 (Vue)" }
|
|
25
|
+
];
|
|
26
|
+
var PARADIGM_OPTIONS = [
|
|
27
|
+
{ value: "functional", label: "Functional", hint: "Hooks, composables, pure functions" },
|
|
28
|
+
{ value: "oop", label: "SOLID OOP", hint: "Classes, services, dependency injection" }
|
|
29
|
+
];
|
|
30
|
+
var STYLING_OPTIONS = [
|
|
31
|
+
{ value: "tailwind", label: "Tailwind CSS", hint: "Utility-first CSS framework" },
|
|
32
|
+
{ value: "scss", label: "SCSS", hint: "Sass preprocessor" },
|
|
33
|
+
{ value: "css-modules", label: "CSS Modules", hint: "Scoped CSS files" },
|
|
34
|
+
{ value: "styled-components", label: "Styled Components", hint: "CSS-in-JS" },
|
|
35
|
+
{ value: "material-ui", label: "Material UI", hint: "MUI component library" },
|
|
36
|
+
{ value: "chakra-ui", label: "Chakra UI", hint: "Accessible component library" },
|
|
37
|
+
{ value: "ant-design", label: "Ant Design", hint: "Enterprise UI components" },
|
|
38
|
+
{ value: "angular-material", label: "Angular Material", hint: "Material Design for Angular" }
|
|
39
|
+
];
|
|
40
|
+
var STATE_OPTIONS = [
|
|
41
|
+
{ value: "none", label: "None", hint: "No state management library" },
|
|
42
|
+
{ value: "redux", label: "Redux Toolkit", hint: "Predictable state container" },
|
|
43
|
+
{ value: "zustand", label: "Zustand", hint: "Lightweight state management" },
|
|
44
|
+
{ value: "mobx", label: "MobX", hint: "Reactive state management" },
|
|
45
|
+
{ value: "pinia", label: "Pinia", hint: "Vue store library" },
|
|
46
|
+
{ value: "rxjs", label: "RxJS", hint: "Reactive extensions" },
|
|
47
|
+
{ value: "ngrx", label: "NgRx", hint: "Redux-inspired Angular state" }
|
|
48
|
+
];
|
|
49
|
+
var EXTRAS_OPTIONS = [
|
|
50
|
+
{ value: "typescript", label: "TypeScript", hint: "Type-safe JavaScript" },
|
|
51
|
+
{ value: "eslint", label: "ESLint", hint: "Code linting" },
|
|
52
|
+
{ value: "prettier", label: "Prettier", hint: "Code formatting" }
|
|
53
|
+
];
|
|
54
|
+
var STYLING_COMPATIBILITY = {
|
|
55
|
+
"tailwind": ["react", "vue", "angular", "vanilla", "nextjs", "nuxt"],
|
|
56
|
+
"scss": ["react", "vue", "angular", "vanilla", "nextjs", "nuxt"],
|
|
57
|
+
"css-modules": ["react", "vue", "vanilla", "nextjs", "nuxt"],
|
|
58
|
+
"styled-components": ["react", "nextjs"],
|
|
59
|
+
"material-ui": ["react", "nextjs"],
|
|
60
|
+
"chakra-ui": ["react", "nextjs"],
|
|
61
|
+
"ant-design": ["react", "vue", "nextjs"],
|
|
62
|
+
"angular-material": ["angular"]
|
|
63
|
+
};
|
|
64
|
+
var STATE_COMPATIBILITY = {
|
|
65
|
+
"none": ["react", "vue", "angular", "vanilla", "nextjs", "nuxt"],
|
|
66
|
+
"redux": ["react", "nextjs"],
|
|
67
|
+
"zustand": ["react", "nextjs"],
|
|
68
|
+
"mobx": ["react", "nextjs"],
|
|
69
|
+
"pinia": ["vue", "nuxt"],
|
|
70
|
+
"rxjs": ["angular"],
|
|
71
|
+
"ngrx": ["angular"]
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// src/generator.ts
|
|
75
|
+
import path from "path";
|
|
76
|
+
import { fileURLToPath } from "url";
|
|
77
|
+
import fs2 from "fs-extra";
|
|
78
|
+
|
|
79
|
+
// src/utils.ts
|
|
80
|
+
import fs from "fs";
|
|
81
|
+
function validateProjectName(name) {
|
|
82
|
+
if (!name) return "Project name is required";
|
|
83
|
+
if (!/^[a-zA-Z0-9@][a-zA-Z0-9._\-/]*$/.test(name)) {
|
|
84
|
+
return "Invalid name. Use letters, numbers, hyphens, dots.";
|
|
85
|
+
}
|
|
86
|
+
if (name.length > 128) return "Name too long (max 128 chars)";
|
|
87
|
+
return void 0;
|
|
88
|
+
}
|
|
89
|
+
function isDirectoryEmpty(dirPath) {
|
|
90
|
+
if (!fs.existsSync(dirPath)) return true;
|
|
91
|
+
const files = fs.readdirSync(dirPath);
|
|
92
|
+
return files.length === 0 || files.length === 1 && files[0] === ".git";
|
|
93
|
+
}
|
|
94
|
+
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
95
|
+
".ts",
|
|
96
|
+
".tsx",
|
|
97
|
+
".js",
|
|
98
|
+
".jsx",
|
|
99
|
+
".json",
|
|
100
|
+
".html",
|
|
101
|
+
".css",
|
|
102
|
+
".scss",
|
|
103
|
+
".vue",
|
|
104
|
+
".md",
|
|
105
|
+
".yaml",
|
|
106
|
+
".yml",
|
|
107
|
+
".toml",
|
|
108
|
+
".svg",
|
|
109
|
+
".mjs",
|
|
110
|
+
".cjs",
|
|
111
|
+
".env",
|
|
112
|
+
".prettierrc",
|
|
113
|
+
".eslintrc"
|
|
114
|
+
]);
|
|
115
|
+
function isTextFile(filename) {
|
|
116
|
+
const ext = filename.slice(filename.lastIndexOf("."));
|
|
117
|
+
return TEXT_EXTENSIONS.has(ext) || filename.startsWith(".");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/generator.ts
|
|
121
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
122
|
+
var __dirname = path.dirname(__filename);
|
|
123
|
+
var TEMPLATES_DIR = path.resolve(__dirname, "..", "templates");
|
|
124
|
+
async function generateProject(config) {
|
|
125
|
+
const { targetDir, framework, templateType, paradigm, styling, stateManagement, extras, projectName } = config;
|
|
126
|
+
await fs2.ensureDir(targetDir);
|
|
127
|
+
const frameworkDir = path.join(TEMPLATES_DIR, framework);
|
|
128
|
+
if (await fs2.pathExists(frameworkDir)) {
|
|
129
|
+
await copyTemplateDir(frameworkDir, targetDir, { projectName });
|
|
130
|
+
}
|
|
131
|
+
const overlayDir = path.join(TEMPLATES_DIR, "overlays", templateType, framework);
|
|
132
|
+
if (await fs2.pathExists(overlayDir)) {
|
|
133
|
+
await copyTemplateDir(overlayDir, targetDir, { projectName });
|
|
134
|
+
}
|
|
135
|
+
const paradigmDir = path.join(TEMPLATES_DIR, "paradigms", paradigm, framework);
|
|
136
|
+
if (await fs2.pathExists(paradigmDir)) {
|
|
137
|
+
await copyTemplateDir(paradigmDir, targetDir, { projectName });
|
|
138
|
+
}
|
|
139
|
+
if (styling) {
|
|
140
|
+
await applyOverlayWithDeps(
|
|
141
|
+
path.join(TEMPLATES_DIR, "styling", styling),
|
|
142
|
+
targetDir,
|
|
143
|
+
config
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (stateManagement && stateManagement !== "none") {
|
|
147
|
+
await applyOverlayWithDeps(
|
|
148
|
+
path.join(TEMPLATES_DIR, "state", stateManagement),
|
|
149
|
+
targetDir,
|
|
150
|
+
config
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
for (const extra of extras) {
|
|
154
|
+
await applyOverlayWithDeps(
|
|
155
|
+
path.join(TEMPLATES_DIR, "extras", extra),
|
|
156
|
+
targetDir,
|
|
157
|
+
config
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
const baseDir = path.join(TEMPLATES_DIR, "base");
|
|
161
|
+
if (await fs2.pathExists(baseDir)) {
|
|
162
|
+
await copyTemplateDir(baseDir, targetDir, { projectName });
|
|
163
|
+
}
|
|
164
|
+
await finalizePackageJson(targetDir, config);
|
|
165
|
+
}
|
|
166
|
+
async function applyOverlayWithDeps(overlayDir, targetDir, config) {
|
|
167
|
+
if (!await fs2.pathExists(overlayDir)) return;
|
|
168
|
+
const depsFile = path.join(overlayDir, "deps.json");
|
|
169
|
+
if (await fs2.pathExists(depsFile)) {
|
|
170
|
+
const deps = await fs2.readJson(depsFile);
|
|
171
|
+
await mergeDependencies(targetDir, deps);
|
|
172
|
+
}
|
|
173
|
+
const configDir = path.join(overlayDir, "config");
|
|
174
|
+
if (await fs2.pathExists(configDir)) {
|
|
175
|
+
await copyTemplateDir(configDir, targetDir, { projectName: config.projectName });
|
|
176
|
+
}
|
|
177
|
+
const fwDir = path.join(overlayDir, config.framework);
|
|
178
|
+
if (await fs2.pathExists(fwDir)) {
|
|
179
|
+
await copyTemplateDir(fwDir, targetDir, { projectName: config.projectName });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function copyTemplateDir(srcDir, destDir, vars) {
|
|
183
|
+
const entries = await fs2.readdir(srcDir, { withFileTypes: true });
|
|
184
|
+
for (const entry of entries) {
|
|
185
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
186
|
+
let destName = entry.name;
|
|
187
|
+
if (destName.startsWith("_")) {
|
|
188
|
+
destName = "." + destName.slice(1);
|
|
189
|
+
}
|
|
190
|
+
const destPath = path.join(destDir, destName);
|
|
191
|
+
if (entry.isDirectory()) {
|
|
192
|
+
await fs2.ensureDir(destPath);
|
|
193
|
+
await copyTemplateDir(srcPath, destPath, vars);
|
|
194
|
+
} else {
|
|
195
|
+
await fs2.ensureDir(path.dirname(destPath));
|
|
196
|
+
if (isTextFile(entry.name)) {
|
|
197
|
+
let content = await fs2.readFile(srcPath, "utf-8");
|
|
198
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
199
|
+
content = content.replaceAll(`{{${key}}}`, value);
|
|
200
|
+
}
|
|
201
|
+
await fs2.writeFile(destPath, content, "utf-8");
|
|
202
|
+
} else {
|
|
203
|
+
await fs2.copy(srcPath, destPath);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async function mergeDependencies(targetDir, newDeps) {
|
|
209
|
+
const pkgPath = path.join(targetDir, "package.json");
|
|
210
|
+
if (!await fs2.pathExists(pkgPath)) return;
|
|
211
|
+
const pkg = await fs2.readJson(pkgPath);
|
|
212
|
+
if (newDeps.dependencies) {
|
|
213
|
+
pkg.dependencies = { ...pkg.dependencies || {}, ...newDeps.dependencies };
|
|
214
|
+
}
|
|
215
|
+
if (newDeps.devDependencies) {
|
|
216
|
+
pkg.devDependencies = { ...pkg.devDependencies || {}, ...newDeps.devDependencies };
|
|
217
|
+
}
|
|
218
|
+
await fs2.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
219
|
+
}
|
|
220
|
+
async function finalizePackageJson(targetDir, config) {
|
|
221
|
+
const pkgPath = path.join(targetDir, "package.json");
|
|
222
|
+
if (!await fs2.pathExists(pkgPath)) return;
|
|
223
|
+
const pkg = await fs2.readJson(pkgPath);
|
|
224
|
+
pkg.name = config.projectName;
|
|
225
|
+
if (pkg.dependencies) {
|
|
226
|
+
pkg.dependencies = sortObject(pkg.dependencies);
|
|
227
|
+
}
|
|
228
|
+
if (pkg.devDependencies) {
|
|
229
|
+
pkg.devDependencies = sortObject(pkg.devDependencies);
|
|
230
|
+
}
|
|
231
|
+
await fs2.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
232
|
+
}
|
|
233
|
+
function sortObject(obj) {
|
|
234
|
+
return Object.fromEntries(
|
|
235
|
+
Object.entries(obj).sort(([a], [b]) => a.localeCompare(b))
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/cli.ts
|
|
240
|
+
async function runCli() {
|
|
241
|
+
console.log("");
|
|
242
|
+
p.intro(pc.bgCyan(pc.black(" webforge-cli ")) + pc.dim(" \u2014 project template generator"));
|
|
243
|
+
const argProjectName = process.argv[2];
|
|
244
|
+
const config = await p.group(
|
|
245
|
+
{
|
|
246
|
+
projectName: () => p.text({
|
|
247
|
+
message: "Project name:",
|
|
248
|
+
placeholder: "my-awesome-project",
|
|
249
|
+
initialValue: argProjectName,
|
|
250
|
+
validate: (value) => validateProjectName(value ?? "")
|
|
251
|
+
}),
|
|
252
|
+
templateType: () => p.select({
|
|
253
|
+
message: "Select a template type:",
|
|
254
|
+
options: TEMPLATE_OPTIONS.map((t) => ({
|
|
255
|
+
value: t.value,
|
|
256
|
+
label: t.label,
|
|
257
|
+
hint: t.hint
|
|
258
|
+
}))
|
|
259
|
+
}),
|
|
260
|
+
framework: () => p.select({
|
|
261
|
+
message: "Select a framework:",
|
|
262
|
+
options: FRAMEWORK_OPTIONS.map((f) => ({
|
|
263
|
+
value: f.value,
|
|
264
|
+
label: f.label,
|
|
265
|
+
hint: f.hint
|
|
266
|
+
}))
|
|
267
|
+
}),
|
|
268
|
+
paradigm: () => p.select({
|
|
269
|
+
message: "Select a coding paradigm:",
|
|
270
|
+
options: PARADIGM_OPTIONS.map((p2) => ({
|
|
271
|
+
value: p2.value,
|
|
272
|
+
label: p2.label,
|
|
273
|
+
hint: p2.hint
|
|
274
|
+
}))
|
|
275
|
+
}),
|
|
276
|
+
styling: ({ results }) => {
|
|
277
|
+
const fw = results.framework;
|
|
278
|
+
const compatible = STYLING_OPTIONS.filter(
|
|
279
|
+
(s) => STYLING_COMPATIBILITY[s.value]?.includes(fw)
|
|
280
|
+
);
|
|
281
|
+
return p.select({
|
|
282
|
+
message: "Select a styling solution:",
|
|
283
|
+
options: compatible.map((s) => ({
|
|
284
|
+
value: s.value,
|
|
285
|
+
label: s.label,
|
|
286
|
+
hint: s.hint
|
|
287
|
+
}))
|
|
288
|
+
});
|
|
289
|
+
},
|
|
290
|
+
stateManagement: ({ results }) => {
|
|
291
|
+
const fw = results.framework;
|
|
292
|
+
const compatible = STATE_OPTIONS.filter(
|
|
293
|
+
(s) => STATE_COMPATIBILITY[s.value]?.includes(fw)
|
|
294
|
+
);
|
|
295
|
+
return p.select({
|
|
296
|
+
message: "Select state management:",
|
|
297
|
+
options: compatible.map((s) => ({
|
|
298
|
+
value: s.value,
|
|
299
|
+
label: s.label,
|
|
300
|
+
hint: s.hint
|
|
301
|
+
}))
|
|
302
|
+
});
|
|
303
|
+
},
|
|
304
|
+
extras: () => p.multiselect({
|
|
305
|
+
message: "Select additional tools:",
|
|
306
|
+
options: EXTRAS_OPTIONS.map((e) => ({
|
|
307
|
+
value: e.value,
|
|
308
|
+
label: e.label,
|
|
309
|
+
hint: e.hint
|
|
310
|
+
})),
|
|
311
|
+
required: false
|
|
312
|
+
})
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
onCancel: () => {
|
|
316
|
+
p.cancel("Operation cancelled.");
|
|
317
|
+
process.exit(0);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
);
|
|
321
|
+
const targetDir = path2.resolve(process.cwd(), config.projectName);
|
|
322
|
+
if (!isDirectoryEmpty(targetDir)) {
|
|
323
|
+
const shouldOverwrite = await p.confirm({
|
|
324
|
+
message: `Directory "${config.projectName}" is not empty. Overwrite?`,
|
|
325
|
+
initialValue: false
|
|
326
|
+
});
|
|
327
|
+
if (p.isCancel(shouldOverwrite) || !shouldOverwrite) {
|
|
328
|
+
p.cancel("Operation cancelled.");
|
|
329
|
+
process.exit(0);
|
|
330
|
+
}
|
|
331
|
+
fs3.rmSync(targetDir, { recursive: true, force: true });
|
|
332
|
+
}
|
|
333
|
+
const projectConfig = {
|
|
334
|
+
projectName: config.projectName,
|
|
335
|
+
templateType: config.templateType,
|
|
336
|
+
framework: config.framework,
|
|
337
|
+
paradigm: config.paradigm,
|
|
338
|
+
styling: config.styling,
|
|
339
|
+
stateManagement: config.stateManagement,
|
|
340
|
+
extras: config.extras,
|
|
341
|
+
targetDir
|
|
342
|
+
};
|
|
343
|
+
p.log.info(
|
|
344
|
+
`${pc.bold("Configuration:")}
|
|
345
|
+
Template: ${pc.cyan(config.templateType)}
|
|
346
|
+
Framework: ${pc.cyan(config.framework)}
|
|
347
|
+
Paradigm: ${pc.cyan(config.paradigm)}
|
|
348
|
+
Styling: ${pc.cyan(String(config.styling))}
|
|
349
|
+
State: ${pc.cyan(String(config.stateManagement))}
|
|
350
|
+
Extras: ${pc.cyan(config.extras.join(", ") || "none")}`
|
|
351
|
+
);
|
|
352
|
+
const spinner2 = p.spinner();
|
|
353
|
+
spinner2.start("Generating project...");
|
|
354
|
+
try {
|
|
355
|
+
await generateProject(projectConfig);
|
|
356
|
+
spinner2.stop("Project generated successfully!");
|
|
357
|
+
} catch (err) {
|
|
358
|
+
spinner2.stop("Generation failed.");
|
|
359
|
+
p.log.error(String(err));
|
|
360
|
+
process.exit(1);
|
|
361
|
+
}
|
|
362
|
+
const pkgManager = detectPackageManager();
|
|
363
|
+
p.note(
|
|
364
|
+
[
|
|
365
|
+
`cd ${config.projectName}`,
|
|
366
|
+
`${pkgManager} install`,
|
|
367
|
+
`${pkgManager === "npm" ? "npm run" : pkgManager} dev`
|
|
368
|
+
].join("\n"),
|
|
369
|
+
"Next steps"
|
|
370
|
+
);
|
|
371
|
+
p.outro(pc.green("Happy coding!"));
|
|
372
|
+
}
|
|
373
|
+
function detectPackageManager() {
|
|
374
|
+
const ua = process.env.npm_config_user_agent;
|
|
375
|
+
if (!ua) return "npm";
|
|
376
|
+
if (ua.startsWith("pnpm")) return "pnpm";
|
|
377
|
+
if (ua.startsWith("yarn")) return "yarn";
|
|
378
|
+
if (ua.startsWith("bun")) return "bun";
|
|
379
|
+
return "npm";
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/index.ts
|
|
383
|
+
runCli().catch((err) => {
|
|
384
|
+
console.error(err);
|
|
385
|
+
process.exit(1);
|
|
386
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@beknurakhmed/webforge-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Interactive CLI to generate website project templates — landing pages, e-commerce, CRM, dashboards, blogs, portfolios with React, Vue, Angular, Next.js, Nuxt and more",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"webforge-cli": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"dev": "tsup --watch",
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"cli",
|
|
22
|
+
"scaffold",
|
|
23
|
+
"template",
|
|
24
|
+
"generator",
|
|
25
|
+
"react",
|
|
26
|
+
"vue",
|
|
27
|
+
"angular",
|
|
28
|
+
"nextjs",
|
|
29
|
+
"nuxt",
|
|
30
|
+
"vite",
|
|
31
|
+
"tailwind",
|
|
32
|
+
"landing-page",
|
|
33
|
+
"ecommerce",
|
|
34
|
+
"dashboard",
|
|
35
|
+
"crm",
|
|
36
|
+
"blog",
|
|
37
|
+
"portfolio"
|
|
38
|
+
],
|
|
39
|
+
"author": "Beknur <https://github.com/beknurakhmed>",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/beknurakhmed/webforge-cli.git"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/beknurakhmed/webforge-cli#readme",
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/beknurakhmed/webforge-cli/issues"
|
|
47
|
+
},
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=18.0.0"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@clack/prompts": "^1.0.1",
|
|
54
|
+
"fs-extra": "^11.3.3",
|
|
55
|
+
"picocolors": "^1.1.1"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/fs-extra": "^11.0.4",
|
|
59
|
+
"@types/node": "^25.3.0",
|
|
60
|
+
"tsup": "^8.5.1",
|
|
61
|
+
"typescript": "^5.9.3"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"newProjectRoot": "projects",
|
|
5
|
+
"projects": {
|
|
6
|
+
"app": {
|
|
7
|
+
"projectType": "application",
|
|
8
|
+
"root": "",
|
|
9
|
+
"sourceRoot": "src",
|
|
10
|
+
"prefix": "app",
|
|
11
|
+
"architect": {
|
|
12
|
+
"build": {
|
|
13
|
+
"builder": "@angular-devkit/build-angular:application",
|
|
14
|
+
"options": {
|
|
15
|
+
"outputPath": "dist",
|
|
16
|
+
"index": "src/index.html",
|
|
17
|
+
"browser": "src/main.ts",
|
|
18
|
+
"tsConfig": "tsconfig.json",
|
|
19
|
+
"assets": ["{ \"glob\": \"**/*\", \"input\": \"public\" }"],
|
|
20
|
+
"styles": ["src/styles.css"],
|
|
21
|
+
"scripts": []
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"serve": {
|
|
25
|
+
"builder": "@angular-devkit/build-angular:dev-server",
|
|
26
|
+
"configurations": {
|
|
27
|
+
"development": {
|
|
28
|
+
"buildTarget": "app:build:development"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"defaultConfiguration": "development"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|