@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.
Files changed (136) hide show
  1. package/README.md +212 -0
  2. package/dist/index.js +386 -0
  3. package/package.json +63 -0
  4. package/templates/angular/angular.json +36 -0
  5. package/templates/angular/package.json +29 -0
  6. package/templates/angular/src/app/app.component.ts +27 -0
  7. package/templates/angular/src/index.html +11 -0
  8. package/templates/angular/src/main.ts +4 -0
  9. package/templates/angular/src/styles.css +24 -0
  10. package/templates/angular/tsconfig.json +27 -0
  11. package/templates/base/README.md +16 -0
  12. package/templates/base/_gitignore +11 -0
  13. package/templates/extras/eslint/config/eslint.config.js +18 -0
  14. package/templates/extras/eslint/deps.json +7 -0
  15. package/templates/extras/prettier/config/.prettierignore +6 -0
  16. package/templates/extras/prettier/config/.prettierrc +7 -0
  17. package/templates/extras/prettier/deps.json +5 -0
  18. package/templates/nextjs/next.config.ts +5 -0
  19. package/templates/nextjs/package.json +16 -0
  20. package/templates/nextjs/src/app/globals.css +36 -0
  21. package/templates/nextjs/src/app/layout.tsx +19 -0
  22. package/templates/nextjs/src/app/page.tsx +8 -0
  23. package/templates/nextjs/tsconfig.json +21 -0
  24. package/templates/nuxt/app.vue +3 -0
  25. package/templates/nuxt/assets/css/main.css +24 -0
  26. package/templates/nuxt/nuxt.config.ts +5 -0
  27. package/templates/nuxt/package.json +17 -0
  28. package/templates/nuxt/pages/index.vue +20 -0
  29. package/templates/nuxt/tsconfig.json +3 -0
  30. package/templates/overlays/blog/react/src/App.css +41 -0
  31. package/templates/overlays/blog/react/src/App.tsx +39 -0
  32. package/templates/overlays/blog/react/src/components/BlogFooter.tsx +7 -0
  33. package/templates/overlays/blog/react/src/components/BlogHeader.tsx +15 -0
  34. package/templates/overlays/blog/react/src/components/BlogSidebar.tsx +26 -0
  35. package/templates/overlays/blog/react/src/components/PostList.tsx +27 -0
  36. package/templates/overlays/crm/react/src/App.css +48 -0
  37. package/templates/overlays/crm/react/src/App.tsx +49 -0
  38. package/templates/overlays/crm/react/src/components/ContactsTable.tsx +40 -0
  39. package/templates/overlays/crm/react/src/components/Filters.tsx +22 -0
  40. package/templates/overlays/crm/react/src/components/Sidebar.tsx +25 -0
  41. package/templates/overlays/crm/react/src/components/StatsCards.tsx +26 -0
  42. package/templates/overlays/dashboard/react/src/App.css +64 -0
  43. package/templates/overlays/dashboard/react/src/App.tsx +30 -0
  44. package/templates/overlays/dashboard/react/src/components/ChartPlaceholder.tsx +18 -0
  45. package/templates/overlays/dashboard/react/src/components/DataTable.tsx +44 -0
  46. package/templates/overlays/dashboard/react/src/components/KPICards.tsx +22 -0
  47. package/templates/overlays/dashboard/react/src/components/Sidebar.tsx +24 -0
  48. package/templates/overlays/ecommerce/react/src/App.css +82 -0
  49. package/templates/overlays/ecommerce/react/src/App.tsx +68 -0
  50. package/templates/overlays/ecommerce/react/src/components/Cart.tsx +47 -0
  51. package/templates/overlays/ecommerce/react/src/components/Footer.tsx +29 -0
  52. package/templates/overlays/ecommerce/react/src/components/Header.tsx +26 -0
  53. package/templates/overlays/ecommerce/react/src/components/ProductGrid.tsx +32 -0
  54. package/templates/overlays/ecommerce/vue/src/App.vue +44 -0
  55. package/templates/overlays/ecommerce/vue/src/components/CartPanel.vue +46 -0
  56. package/templates/overlays/ecommerce/vue/src/components/ProductGrid.vue +40 -0
  57. package/templates/overlays/ecommerce/vue/src/components/StoreFooter.vue +17 -0
  58. package/templates/overlays/ecommerce/vue/src/components/StoreHeader.vue +33 -0
  59. package/templates/overlays/landing/angular/src/app/app.component.ts +21 -0
  60. package/templates/overlays/landing/angular/src/app/components/cta.component.ts +24 -0
  61. package/templates/overlays/landing/angular/src/app/components/features.component.ts +42 -0
  62. package/templates/overlays/landing/angular/src/app/components/footer.component.ts +45 -0
  63. package/templates/overlays/landing/angular/src/app/components/hero.component.ts +41 -0
  64. package/templates/overlays/landing/nextjs/src/app/components/CTA.tsx +12 -0
  65. package/templates/overlays/landing/nextjs/src/app/components/Features.tsx +26 -0
  66. package/templates/overlays/landing/nextjs/src/app/components/Footer.tsx +27 -0
  67. package/templates/overlays/landing/nextjs/src/app/components/Hero.tsx +22 -0
  68. package/templates/overlays/landing/nextjs/src/app/globals.css +49 -0
  69. package/templates/overlays/landing/nextjs/src/app/page.tsx +15 -0
  70. package/templates/overlays/landing/nuxt/components/LandingCta.vue +18 -0
  71. package/templates/overlays/landing/nuxt/components/LandingFeatures.vue +36 -0
  72. package/templates/overlays/landing/nuxt/components/LandingFooter.vue +29 -0
  73. package/templates/overlays/landing/nuxt/components/LandingHero.vue +35 -0
  74. package/templates/overlays/landing/nuxt/pages/index.vue +15 -0
  75. package/templates/overlays/landing/react/src/App.css +70 -0
  76. package/templates/overlays/landing/react/src/App.tsx +18 -0
  77. package/templates/overlays/landing/react/src/components/CTA.tsx +14 -0
  78. package/templates/overlays/landing/react/src/components/Features.tsx +50 -0
  79. package/templates/overlays/landing/react/src/components/Footer.tsx +34 -0
  80. package/templates/overlays/landing/react/src/components/Hero.tsx +22 -0
  81. package/templates/overlays/landing/vanilla/src/main.ts +68 -0
  82. package/templates/overlays/landing/vanilla/src/style.css +43 -0
  83. package/templates/overlays/landing/vue/src/App.vue +19 -0
  84. package/templates/overlays/landing/vue/src/components/AppFooter.vue +44 -0
  85. package/templates/overlays/landing/vue/src/components/CTA.vue +21 -0
  86. package/templates/overlays/landing/vue/src/components/Features.vue +36 -0
  87. package/templates/overlays/landing/vue/src/components/Hero.vue +35 -0
  88. package/templates/overlays/portfolio/react/src/App.css +81 -0
  89. package/templates/overlays/portfolio/react/src/App.tsx +20 -0
  90. package/templates/overlays/portfolio/react/src/components/ContactForm.tsx +29 -0
  91. package/templates/overlays/portfolio/react/src/components/HeroSection.tsx +24 -0
  92. package/templates/overlays/portfolio/react/src/components/PortfolioFooter.tsx +14 -0
  93. package/templates/overlays/portfolio/react/src/components/Projects.tsx +33 -0
  94. package/templates/overlays/portfolio/react/src/components/Skills.tsx +27 -0
  95. package/templates/react/index.html +13 -0
  96. package/templates/react/package.json +19 -0
  97. package/templates/react/public/vite.svg +1 -0
  98. package/templates/react/src/App.css +11 -0
  99. package/templates/react/src/App.tsx +12 -0
  100. package/templates/react/src/index.css +42 -0
  101. package/templates/react/src/main.tsx +10 -0
  102. package/templates/react/vite.config.ts +6 -0
  103. package/templates/state/mobx/deps.json +6 -0
  104. package/templates/state/mobx/react/src/store/AppStore.ts +15 -0
  105. package/templates/state/ngrx/angular/src/app/store/app.state.ts +22 -0
  106. package/templates/state/ngrx/deps.json +7 -0
  107. package/templates/state/pinia/deps.json +5 -0
  108. package/templates/state/pinia/vue/src/store/useAppStore.ts +15 -0
  109. package/templates/state/redux/deps.json +6 -0
  110. package/templates/state/redux/react/src/store/counterSlice.ts +20 -0
  111. package/templates/state/redux/react/src/store/index.ts +11 -0
  112. package/templates/state/rxjs/angular/src/app/services/app-state.service.ts +18 -0
  113. package/templates/state/rxjs/deps.json +3 -0
  114. package/templates/state/zustand/deps.json +5 -0
  115. package/templates/state/zustand/react/src/store/useStore.ts +15 -0
  116. package/templates/styling/angular-material/deps.json +6 -0
  117. package/templates/styling/ant-design/deps.json +5 -0
  118. package/templates/styling/chakra-ui/deps.json +5 -0
  119. package/templates/styling/css-modules/deps.json +3 -0
  120. package/templates/styling/material-ui/deps.json +7 -0
  121. package/templates/styling/scss/deps.json +5 -0
  122. package/templates/styling/styled-components/deps.json +5 -0
  123. package/templates/styling/tailwind/config/postcss.config.js +5 -0
  124. package/templates/styling/tailwind/config/tailwind.config.js +11 -0
  125. package/templates/styling/tailwind/deps.json +6 -0
  126. package/templates/vanilla/index.html +13 -0
  127. package/templates/vanilla/package.json +14 -0
  128. package/templates/vanilla/src/main.ts +9 -0
  129. package/templates/vanilla/src/style.css +36 -0
  130. package/templates/vanilla/vite.config.ts +3 -0
  131. package/templates/vue/index.html +13 -0
  132. package/templates/vue/package.json +18 -0
  133. package/templates/vue/src/App.vue +23 -0
  134. package/templates/vue/src/main.ts +5 -0
  135. package/templates/vue/src/style.css +39 -0
  136. 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
+ }