leedstack 3.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/LICENSE +21 -0
- package/README.md +364 -0
- package/bin/create-stack.js +277 -0
- package/package.json +60 -0
- package/tools/templates/backend/go-echo/backend/.env.example +10 -0
- package/tools/templates/backend/go-echo/backend/cmd/server/main.go.ejs +57 -0
- package/tools/templates/backend/go-echo/backend/go.mod.ejs +10 -0
- package/tools/templates/backend/go-echo/backend/internal/handlers/example.go +15 -0
- package/tools/templates/backend/go-echo/backend/internal/handlers/health.go +13 -0
- package/tools/templates/backend/java-spring/backend/.env.example +10 -0
- package/tools/templates/backend/java-spring/backend/pom.xml.ejs +64 -0
- package/tools/templates/backend/java-spring/backend/src/main/java/com/app/Application.java.ejs +11 -0
- package/tools/templates/backend/java-spring/backend/src/main/java/com/app/config/SecurityConfig.java.ejs +64 -0
- package/tools/templates/backend/java-spring/backend/src/main/java/com/app/controller/ExampleController.java +19 -0
- package/tools/templates/backend/java-spring/backend/src/main/java/com/app/controller/HealthController.java +15 -0
- package/tools/templates/backend/java-spring/backend/src/main/resources/application.yml.ejs +20 -0
- package/tools/templates/backend/node-express/backend/.env.example +10 -0
- package/tools/templates/backend/node-express/backend/.eslintrc.json +21 -0
- package/tools/templates/backend/node-express/backend/.prettierrc +10 -0
- package/tools/templates/backend/node-express/backend/Dockerfile +52 -0
- package/tools/templates/backend/node-express/backend/README.md +68 -0
- package/tools/templates/backend/node-express/backend/package.json.ejs +37 -0
- package/tools/templates/backend/node-express/backend/src/index.ts.ejs +75 -0
- package/tools/templates/backend/node-express/backend/src/routes/health.ts +54 -0
- package/tools/templates/backend/node-express/backend/tsconfig.json +17 -0
- package/tools/templates/backend/python-fastapi/backend/.env.example +18 -0
- package/tools/templates/backend/python-fastapi/backend/README.md +73 -0
- package/tools/templates/backend/python-fastapi/backend/app/__init__.py +1 -0
- package/tools/templates/backend/python-fastapi/backend/app/main.py.ejs +68 -0
- package/tools/templates/backend/python-fastapi/backend/requirements.txt.ejs +22 -0
- package/tools/templates/base/.dockerignore +16 -0
- package/tools/templates/base/.env.example +31 -0
- package/tools/templates/base/.github/workflows/ci.yml.ejs +124 -0
- package/tools/templates/base/.github/workflows/deploy-separate.yml.example +144 -0
- package/tools/templates/base/.vscode/extensions.json +17 -0
- package/tools/templates/base/.vscode/settings.json +49 -0
- package/tools/templates/base/Makefile +98 -0
- package/tools/templates/base/README.md.ejs +118 -0
- package/tools/templates/base/docker-compose.yml.ejs +49 -0
- package/tools/templates/base/package.json.ejs +30 -0
- package/tools/templates/base/scripts/split-repos.sh +189 -0
- package/tools/templates/db/postgres/backend/java-spring/backend/pom.xml.ejs +81 -0
- package/tools/templates/db/postgres/backend/java-spring/backend/src/main/resources/application.yml.ejs +39 -0
- package/tools/templates/db/postgres/backend/java-spring/backend/src/main/resources/db/migration/V1__init.sql +17 -0
- package/tools/templates/db/postgres/backend/node-express/backend/.env.example +10 -0
- package/tools/templates/db/postgres/backend/node-express/backend/package.json.ejs +32 -0
- package/tools/templates/db/postgres/backend/node-express/backend/prisma/schema.prisma.ejs +39 -0
- package/tools/templates/frontend/angular/frontend/.env.example +9 -0
- package/tools/templates/frontend/angular/frontend/angular.json +66 -0
- package/tools/templates/frontend/angular/frontend/package.json.ejs +31 -0
- package/tools/templates/frontend/angular/frontend/src/app/app.component.ts +30 -0
- package/tools/templates/frontend/angular/frontend/src/app/app.routes.ts +18 -0
- package/tools/templates/frontend/angular/frontend/src/app/components/home.component.ts +24 -0
- package/tools/templates/frontend/angular/frontend/src/app/services/api.service.ts +48 -0
- package/tools/templates/frontend/angular/frontend/src/favicon.ico +1 -0
- package/tools/templates/frontend/angular/frontend/src/index.html +13 -0
- package/tools/templates/frontend/angular/frontend/src/main.ts +10 -0
- package/tools/templates/frontend/angular/frontend/src/styles.css +31 -0
- package/tools/templates/frontend/angular/frontend/tsconfig.app.json +9 -0
- package/tools/templates/frontend/angular/frontend/tsconfig.json +27 -0
- package/tools/templates/frontend/nextjs/frontend/.env.example +9 -0
- package/tools/templates/frontend/nextjs/frontend/next.config.js +37 -0
- package/tools/templates/frontend/nextjs/frontend/package.json.ejs +25 -0
- package/tools/templates/frontend/nextjs/frontend/src/app/globals.css +31 -0
- package/tools/templates/frontend/nextjs/frontend/src/app/layout.tsx +36 -0
- package/tools/templates/frontend/nextjs/frontend/src/app/page.tsx +19 -0
- package/tools/templates/frontend/nextjs/frontend/src/lib/api.ts +45 -0
- package/tools/templates/frontend/nextjs/frontend/tsconfig.json +27 -0
- package/tools/templates/frontend/react/frontend/.env.example +9 -0
- package/tools/templates/frontend/react/frontend/.eslintrc.json +32 -0
- package/tools/templates/frontend/react/frontend/.prettierrc +10 -0
- package/tools/templates/frontend/react/frontend/Dockerfile +37 -0
- package/tools/templates/frontend/react/frontend/README.md +54 -0
- package/tools/templates/frontend/react/frontend/index.html +13 -0
- package/tools/templates/frontend/react/frontend/nginx.conf +35 -0
- package/tools/templates/frontend/react/frontend/package.json.ejs +41 -0
- package/tools/templates/frontend/react/frontend/public/vite.svg +4 -0
- package/tools/templates/frontend/react/frontend/src/App.css +65 -0
- package/tools/templates/frontend/react/frontend/src/App.jsx +41 -0
- package/tools/templates/frontend/react/frontend/src/assets/react.svg +7 -0
- package/tools/templates/frontend/react/frontend/src/components/ErrorBoundary.jsx +62 -0
- package/tools/templates/frontend/react/frontend/src/components/Home.jsx +58 -0
- package/tools/templates/frontend/react/frontend/src/components/__tests__/Home.test.jsx +74 -0
- package/tools/templates/frontend/react/frontend/src/index.css +31 -0
- package/tools/templates/frontend/react/frontend/src/lib/api.js +42 -0
- package/tools/templates/frontend/react/frontend/src/lib/env.js +58 -0
- package/tools/templates/frontend/react/frontend/src/main.jsx +16 -0
- package/tools/templates/frontend/react/frontend/src/setupTests.js +8 -0
- package/tools/templates/frontend/react/frontend/vite.config.js +30 -0
- package/tools/templates/frontend/react/frontend/vitest.config.js +20 -0
- package/tools/templates/frontend/svelte/frontend/.env.example +9 -0
- package/tools/templates/frontend/svelte/frontend/package.json.ejs +21 -0
- package/tools/templates/frontend/svelte/frontend/src/app.html +12 -0
- package/tools/templates/frontend/svelte/frontend/src/lib/api.ts +45 -0
- package/tools/templates/frontend/svelte/frontend/src/routes/+layout.svelte +56 -0
- package/tools/templates/frontend/svelte/frontend/src/routes/+page.svelte +20 -0
- package/tools/templates/frontend/svelte/frontend/static/favicon.png +1 -0
- package/tools/templates/frontend/svelte/frontend/svelte.config.js +10 -0
- package/tools/templates/frontend/svelte/frontend/vite.config.js +9 -0
- package/tools/templates/frontend/vue/frontend/.env.example +9 -0
- package/tools/templates/frontend/vue/frontend/index.html +13 -0
- package/tools/templates/frontend/vue/frontend/package.json.ejs +20 -0
- package/tools/templates/frontend/vue/frontend/src/App.vue +60 -0
- package/tools/templates/frontend/vue/frontend/src/lib/api.js +42 -0
- package/tools/templates/frontend/vue/frontend/src/main.js +33 -0
- package/tools/templates/frontend/vue/frontend/src/views/ApiTest.vue +39 -0
- package/tools/templates/frontend/vue/frontend/src/views/Home.vue +30 -0
- package/tools/templates/frontend/vue/frontend/vite.config.js +9 -0
- package/tools/templates/modules/admin/backend/java-spring/backend/src/main/java/com/app/controller/AdminController.java +41 -0
- package/tools/templates/modules/admin/backend/java-spring/backend/src/main/java/com/app/entity/User.java +55 -0
- package/tools/templates/modules/admin/backend/java-spring/backend/src/main/java/com/app/repository/UserRepository.java +8 -0
- package/tools/templates/modules/admin/frontend/svelte/frontend/src/routes/dashboard/+page.svelte +93 -0
- package/tools/templates/modules/auth/backend/node-express/backend/src/middleware/auth.ts +42 -0
- package/tools/templates/modules/auth/frontend/svelte/frontend/src/hooks.client.ts +3 -0
- package/tools/templates/modules/auth/frontend/svelte/frontend/src/lib/auth.ts +104 -0
- package/tools/templates/modules/auth/frontend/svelte/frontend/src/routes/callback/+page.svelte +18 -0
- package/tools/templates/modules/auth/frontend/svelte/frontend/src/routes/login/+page.svelte +12 -0
- package/tools/templates/modules/chatbot/backend/node-express/backend/src/index.ts.ejs +69 -0
- package/tools/templates/modules/chatbot/backend/node-express/backend/src/routes/chatbot.ts.ejs +37 -0
- package/tools/templates/modules/chatbot/backend/node-express/backend/src/services/chatbotService.ts +124 -0
- package/tools/templates/modules/chatbot/backend/python-fastapi/backend/app/main.py.ejs +69 -0
- package/tools/templates/modules/chatbot/backend/python-fastapi/backend/app/routes/chatbot.py +38 -0
- package/tools/templates/modules/chatbot/backend/python-fastapi/backend/app/services/chatbot_service.py +123 -0
- package/tools/templates/modules/chatbot/backend/python-fastapi/backend/requirements.txt +1 -0
- package/tools/templates/modules/chatbot/frontend/react/frontend/src/App.jsx.ejs +74 -0
- package/tools/templates/modules/chatbot/frontend/react/frontend/src/components/Chatbot.css +198 -0
- package/tools/templates/modules/chatbot/frontend/react/frontend/src/components/Chatbot.jsx +113 -0
- package/tools/templates/modules/contact/backend/java-spring/backend/src/main/java/com/app/controller/ContactController.java +29 -0
- package/tools/templates/modules/contact/backend/java-spring/backend/src/main/java/com/app/entity/ContactMessage.java +66 -0
- package/tools/templates/modules/contact/backend/java-spring/backend/src/main/java/com/app/repository/ContactMessageRepository.java +8 -0
- package/tools/templates/modules/contact/backend/java-spring/backend/src/main/resources/db/migration/V2__contact.sql +7 -0
- package/tools/templates/modules/contact/frontend/svelte/frontend/src/routes/contact/+page.svelte +80 -0
- package/tools/templates/modules/payments/backend/java-spring/backend/src/main/java/com/app/controller/PaymentController.java +69 -0
- package/tools/templates/modules/payments/backend/node-express/backend/src/routes/payments.ts +30 -0
- package/tools/templates/modules/payments/backend/node-express/backend/src/routes/webhook.ts +36 -0
- package/tools/templates/modules/payments/frontend/svelte/frontend/src/lib/payments.ts +28 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Colby Leed
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# leedstack
|
|
2
|
+
|
|
3
|
+
> One-command scaffolder for full-stack web applications with pluggable stacks and modern defaults.
|
|
4
|
+
|
|
5
|
+
Generate production-ready full-stack applications in seconds with your choice of frontend, backend, database, and optional modules. The recommended default is React + Go (Echo) with Postgres.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **60+ Stack Combinations**: Mix and match your preferred technologies
|
|
10
|
+
- **âŔ” Lightning Fast**: From zero to coding in <30 seconds with `make setup`
|
|
11
|
+
- **ðŸŔ⬠Production-Ready**: Docker, CI/CD, testing, monitoring - all included
|
|
12
|
+
- **🧪 Automated Testing**: Vitest with example tests, coverage, and test UI
|
|
13
|
+
- **ðŸāā CI/CD Pipeline**: GitHub Actions workflow for automated testing and deployment
|
|
14
|
+
- **ðŸĀ³ Docker Support**: Multi-stage builds for optimized production images
|
|
15
|
+
- **ðŸāÅ Monitoring**: Health checks, memory tracking, K8s-compatible endpoints
|
|
16
|
+
- **Optimized Performance**: HMR <50ms, builds 20-40x faster with esbuild
|
|
17
|
+
- **Zero Memory Leaks**: Proper cleanup in all components and graceful shutdown
|
|
18
|
+
- **Most Popular Stacks**: React, Vue, Next.js, Python/FastAPI, Node/Express, and more
|
|
19
|
+
- **Auto-Configured Connectivity**: Frontend and backend pre-wired with correct ports and CORS
|
|
20
|
+
- **Modern Defaults**: SSR, code-splitting, tree-shaking, strict types
|
|
21
|
+
- **AI Chatbot Module**: Works out-of-box, upgrades with OpenAI/Anthropic API key
|
|
22
|
+
- **Auth0 Integration**: OIDC/PKCE flow with JWT validation
|
|
23
|
+
- **Stripe Payments**: Checkout + webhook handling
|
|
24
|
+
- **Code Quality Tools**: ESLint, Prettier, Error Boundaries pre-configured
|
|
25
|
+
- **Type-Safe**: TypeScript for frontends and Node backend
|
|
26
|
+
|
|
27
|
+
## Free vs Pro (Planned)
|
|
28
|
+
|
|
29
|
+
The npm package will always include the core scaffolding and base stacks.
|
|
30
|
+
Pro will unlock premium modules and templates (Auth, Payments, Admin, Chatbot, deployment presets).
|
|
31
|
+
|
|
32
|
+
**Pricing (planned)**: one-time purchase for Pro (no subscription).
|
|
33
|
+
**Licensing (planned)**: Pro access will use a simple license key file (e.g. `~/.leedstackrc`) with no heavy DRM.
|
|
34
|
+
|
|
35
|
+
FAQ: Will there be a subscription later? Possibly for updates and new templates, but the initial plan is a one-time purchase.
|
|
36
|
+
|
|
37
|
+
### Pro Module Compatibility (Current)
|
|
38
|
+
|
|
39
|
+
Frontends:
|
|
40
|
+
|
|
41
|
+
| Module | React | Vue | Next.js | Svelte | Angular |
|
|
42
|
+
| --- | --- | --- | --- | --- | --- |
|
|
43
|
+
| Auth | ā | ā | ā | ā | ā |
|
|
44
|
+
| Payments | ā | ā | ā | ā | ā |
|
|
45
|
+
| Admin | ā | ā | ā | ā | ā |
|
|
46
|
+
| Contact | ā | ā | ā | ā | ā |
|
|
47
|
+
| Chatbot | ā | ā | ā | ā | ā |
|
|
48
|
+
|
|
49
|
+
Backends:
|
|
50
|
+
|
|
51
|
+
| Module | node-express | python-fastapi | java-spring | go-echo |
|
|
52
|
+
| --- | --- | --- | --- | --- |
|
|
53
|
+
| Auth | ā | ā | ā | ā |
|
|
54
|
+
| Payments | ā | ā | ā | ā |
|
|
55
|
+
| Admin | ā | ā | ā | ā |
|
|
56
|
+
| Contact | ā | ā | ā | ā |
|
|
57
|
+
| Chatbot | ā | ā | ā | ā |
|
|
58
|
+
|
|
59
|
+
If your selected stack isn't listed above, that module will be unavailable until we add support.
|
|
60
|
+
|
|
61
|
+
### Pro Templates (ZIP Install)
|
|
62
|
+
|
|
63
|
+
If you purchase the Pro templates ZIP, install it like this:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Create the local templates folder
|
|
67
|
+
mkdir -p ~/.leedstack/templates
|
|
68
|
+
|
|
69
|
+
# Unzip the download into that folder (replace the zip path)
|
|
70
|
+
unzip /path/to/leedstack-pro-templates.zip -d ~/.leedstack/templates
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
TODO: Add `leedstack login` and automated template downloads from a private registry.
|
|
74
|
+
|
|
75
|
+
## Quick Start
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx leedstack my-app \
|
|
79
|
+
--frontend react \
|
|
80
|
+
--backend go-echo \
|
|
81
|
+
--db postgres
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Then follow the printed instructions to start your app!
|
|
85
|
+
|
|
86
|
+
## âŔ” Fastest Setup
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npx leedstack my-app --frontend react --backend go-echo --db postgres
|
|
90
|
+
|
|
91
|
+
cd my-app
|
|
92
|
+
make setup # Installs everything + starts database (parallel)
|
|
93
|
+
make dev # Start development servers
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Total time: ~30 seconds to fully operational!** ðŸŔā¬
|
|
97
|
+
|
|
98
|
+
See [PERFORMANCE.md](./PERFORMANCE.md) for optimization details.
|
|
99
|
+
|
|
100
|
+
## ðŸā° Deployment Options
|
|
101
|
+
|
|
102
|
+
**Monorepo (Simple):**
|
|
103
|
+
- Deploy everything together: ~$5-15/month
|
|
104
|
+
- Best for: MVPs, small teams, rapid development
|
|
105
|
+
|
|
106
|
+
**Separate Repos (Cost Optimized):**
|
|
107
|
+
- Deploy frontend and backend separately: **$0/month** possible!
|
|
108
|
+
- Frontend: Vercel/Netlify/Cloudflare (FREE)
|
|
109
|
+
- Backend: Fly.io/Railway (FREE tiers)
|
|
110
|
+
- Database: Supabase/PlanetScale (FREE)
|
|
111
|
+
- Best for: Maximizing free tiers, independent scaling
|
|
112
|
+
|
|
113
|
+
**Split your monorepo:**
|
|
114
|
+
```bash
|
|
115
|
+
./scripts/split-repos.sh
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
See [DEPLOYMENT_STRATEGIES.md](./DEPLOYMENT_STRATEGIES.md) for complete guide.
|
|
119
|
+
|
|
120
|
+
## Supported Stacks
|
|
121
|
+
|
|
122
|
+
### Frontends (5 options)
|
|
123
|
+
- **react** - Vite + React 19 (most popular!)
|
|
124
|
+
- **vue** - Vue 3.5 with Vite (lightweight and fast)
|
|
125
|
+
- **nextjs** - Next.js 15 (React with SSR/SSG)
|
|
126
|
+
- **svelte** - SvelteKit 5.x (innovative and performant)
|
|
127
|
+
- **angular** - Angular Ć¢ā°Ā„19.0.0 (enterprise-ready)
|
|
128
|
+
|
|
129
|
+
### Backends (4 options)
|
|
130
|
+
- **go-echo** - Go 1.22+ with Echo framework (high performance, recommended)
|
|
131
|
+
- **node-express** - TypeScript + Express 5.x, Node 20+ (JavaScript ecosystem)
|
|
132
|
+
- **python-fastapi** - Python 3.11+ with FastAPI (great for AI/ML integration)
|
|
133
|
+
- **java-spring** - Spring Boot 3.3.x, Maven, Java 21 LTS (enterprise standard)
|
|
134
|
+
|
|
135
|
+
### Databases
|
|
136
|
+
- **postgres** - PostgreSQL 16+
|
|
137
|
+
- **mysql** - MySQL 8+
|
|
138
|
+
- **mongodb** - MongoDB 7+
|
|
139
|
+
|
|
140
|
+
### Optional Modules
|
|
141
|
+
- **--auth auth0** - Auth0 OIDC authentication
|
|
142
|
+
- **--payments stripe** - Stripe Checkout integration
|
|
143
|
+
- **contact** - Contact form with DB persistence
|
|
144
|
+
- **admin** - Protected admin dashboard with stats
|
|
145
|
+
- **chatbot** - AI chatbot (works out-of-box, upgrades with OpenAI/Anthropic API key)
|
|
146
|
+
|
|
147
|
+
## Usage
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npx leedstack <appName> \
|
|
151
|
+
--frontend <react|vue|nextjs|svelte|angular> \
|
|
152
|
+
--backend <node-express|python-fastapi|java-spring|go-echo> \
|
|
153
|
+
--db <postgres|mysql|mongodb> \
|
|
154
|
+
[--auth auth0|none] \
|
|
155
|
+
[--payments stripe|none] \
|
|
156
|
+
[contact] [admin]
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Options
|
|
160
|
+
|
|
161
|
+
- `-f, --frontend <type>` (required) - Frontend framework
|
|
162
|
+
- `-b, --backend <type>` (required) - Backend stack
|
|
163
|
+
- `--db <type>` (required) - Database
|
|
164
|
+
- `--auth <type>` - Authentication (default: none)
|
|
165
|
+
- `--payments <type>` - Payments (default: none)
|
|
166
|
+
- Trailing arguments: Additional modules (`contact`, `admin`, `chatbot`)
|
|
167
|
+
|
|
168
|
+
## Examples
|
|
169
|
+
|
|
170
|
+
### Modern Python + React Stack with AI Chatbot
|
|
171
|
+
```bash
|
|
172
|
+
npx leedstack my-saas \
|
|
173
|
+
--frontend react \
|
|
174
|
+
--backend python-fastapi \
|
|
175
|
+
--db postgres \
|
|
176
|
+
--auth auth0 \
|
|
177
|
+
--payments stripe \
|
|
178
|
+
contact admin chatbot
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Recommended Default: React + Go + Postgres
|
|
182
|
+
```bash
|
|
183
|
+
npx leedstack my-app \
|
|
184
|
+
--frontend react \
|
|
185
|
+
--backend go-echo \
|
|
186
|
+
--db postgres
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Vue + Node.js Blog
|
|
190
|
+
```bash
|
|
191
|
+
npx leedstack blog \
|
|
192
|
+
--frontend vue \
|
|
193
|
+
--backend node-express \
|
|
194
|
+
--db mongodb \
|
|
195
|
+
chatbot
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Next.js E-commerce
|
|
199
|
+
```bash
|
|
200
|
+
npx leedstack shop \
|
|
201
|
+
--frontend nextjs \
|
|
202
|
+
--backend node-express \
|
|
203
|
+
--db postgres \
|
|
204
|
+
--payments stripe
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Enterprise Java Stack
|
|
209
|
+
```bash
|
|
210
|
+
npx leedstack enterprise \
|
|
211
|
+
--frontend angular \
|
|
212
|
+
--backend java-spring \
|
|
213
|
+
--db postgres \
|
|
214
|
+
--auth auth0 \
|
|
215
|
+
admin
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Generated Project Structure
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
my-app/
|
|
222
|
+
Ć¢āÅĆ¢āā¬Ć¢ā⬠docker-compose.yml # Database container
|
|
223
|
+
Ć¢āÅĆ¢āā¬Ć¢ā⬠README.md # Project-specific docs
|
|
224
|
+
Ć¢āÅĆ¢āā¬Ć¢ā⬠.env.example # Environment template
|
|
225
|
+
Ć¢āÅĆ¢āā¬Ć¢ā⬠frontend/ # Frontend app
|
|
226
|
+
Ć¢āā Ć¢āÅĆ¢āā¬Ć¢ā⬠src/
|
|
227
|
+
Ć¢āā Ć¢āÅĆ¢āā¬Ć¢ā⬠package.json
|
|
228
|
+
Ć¢āā Ć¢āāĆ¢āā¬Ć¢ā⬠.env.example
|
|
229
|
+
Ć¢āāĆ¢āā¬Ć¢ā⬠backend/ # Backend API
|
|
230
|
+
Ć¢āÅĆ¢āā¬Ć¢ā⬠src/
|
|
231
|
+
Ć¢āāĆ¢āā¬Ć¢ā⬠.env.example
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## What's Included
|
|
235
|
+
|
|
236
|
+
### Base Features
|
|
237
|
+
- Docker Compose for database
|
|
238
|
+
- Health endpoints (`/actuator/health` or `/health`)
|
|
239
|
+
- CORS configuration for development
|
|
240
|
+
- Environment variable management
|
|
241
|
+
- Production-ready defaults
|
|
242
|
+
|
|
243
|
+
### With Auth0 (`--auth auth0`)
|
|
244
|
+
- Frontend: Login/logout flow, token management
|
|
245
|
+
- Backend: JWT validation via JWKS, protected routes
|
|
246
|
+
- Admin scope enforcement for `/api/admin/**` endpoints
|
|
247
|
+
|
|
248
|
+
### With Stripe (`--payments stripe`)
|
|
249
|
+
- Create Checkout sessions
|
|
250
|
+
- Webhook signature verification
|
|
251
|
+
- Payment success handling
|
|
252
|
+
|
|
253
|
+
### With Contact Module
|
|
254
|
+
- Contact form (name, email, message)
|
|
255
|
+
- Database persistence
|
|
256
|
+
- `/api/contact` endpoint
|
|
257
|
+
|
|
258
|
+
### With Admin Module
|
|
259
|
+
- Protected dashboard (requires `admin` scope)
|
|
260
|
+
- User and contact stats
|
|
261
|
+
- `/api/admin/stats` endpoint
|
|
262
|
+
|
|
263
|
+
## Technology Choices
|
|
264
|
+
|
|
265
|
+
### Version Policy
|
|
266
|
+
- Pin majors, float minors/patches using `^` (npm) or latest minor (Maven/Go)
|
|
267
|
+
- Enforce minimum runtimes via `engines` field
|
|
268
|
+
|
|
269
|
+
### Security
|
|
270
|
+
- JWT validation via JWKS (no custom crypto)
|
|
271
|
+
- CORS configurable per environment
|
|
272
|
+
- Secrets via environment variables only
|
|
273
|
+
- Admin routes protected by scope/permission
|
|
274
|
+
|
|
275
|
+
### Efficiency
|
|
276
|
+
- **Frontend**: SSR, code-splitting, lazy-loaded admin, minimal deps
|
|
277
|
+
- **Backend**: JSON-only APIs, no view engines
|
|
278
|
+
- **Docker**: Multi-stage builds, distroless/alpine images
|
|
279
|
+
|
|
280
|
+
## Requirements
|
|
281
|
+
|
|
282
|
+
- **Node.js** Ć¢ā°Ā„20.0.0
|
|
283
|
+
- **Java** 21 LTS (for java-spring backend)
|
|
284
|
+
- **Go** Ć¢ā°Ā„1.22 (for go-echo backend)
|
|
285
|
+
- **Docker** (for running databases)
|
|
286
|
+
- **Maven** (for java-spring backend)
|
|
287
|
+
|
|
288
|
+
## Development
|
|
289
|
+
|
|
290
|
+
### Running Generated Projects
|
|
291
|
+
|
|
292
|
+
1. **Start database**:
|
|
293
|
+
```bash
|
|
294
|
+
docker compose up -d
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
2. **Backend**:
|
|
298
|
+
```bash
|
|
299
|
+
cd backend
|
|
300
|
+
# Java Spring
|
|
301
|
+
mvn spring-boot:run
|
|
302
|
+
|
|
303
|
+
# Node Express
|
|
304
|
+
npm install && npm run dev
|
|
305
|
+
|
|
306
|
+
# Go Echo
|
|
307
|
+
go run ./cmd/server
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
3. **Frontend**:
|
|
311
|
+
```bash
|
|
312
|
+
cd frontend
|
|
313
|
+
cp .env.example .env
|
|
314
|
+
npm install
|
|
315
|
+
npm run dev
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Environment Setup
|
|
319
|
+
|
|
320
|
+
See the generated `README.md` in your project for specific Auth0 and Stripe configuration instructions.
|
|
321
|
+
|
|
322
|
+
## Publishing to npm
|
|
323
|
+
|
|
324
|
+
If you want to publish this package:
|
|
325
|
+
|
|
326
|
+
1. Update `package.json`:
|
|
327
|
+
- Change `name` if needed (must be unique on npm)
|
|
328
|
+
- Set your `author` name and email
|
|
329
|
+
- Update `repository`, `bugs`, `homepage` URLs
|
|
330
|
+
|
|
331
|
+
2. Create npm account: https://www.npmjs.com/signup
|
|
332
|
+
|
|
333
|
+
3. Login and publish:
|
|
334
|
+
```bash
|
|
335
|
+
npm login
|
|
336
|
+
npm publish
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
4. Users can then run:
|
|
340
|
+
```bash
|
|
341
|
+
npx leedstack my-app --frontend react --backend node-express --db postgres
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## License
|
|
345
|
+
|
|
346
|
+
MIT (c) Colby Leed
|
|
347
|
+
|
|
348
|
+
## Contributing
|
|
349
|
+
|
|
350
|
+
Issues and PRs welcome! Please ensure generated projects compile and run before submitting.
|
|
351
|
+
|
|
352
|
+
## Support
|
|
353
|
+
|
|
354
|
+
- Report issues: https://github.com/CTLeed/leedstack/issues
|
|
355
|
+
- Documentation: https://github.com/CTLeed/leedstack
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import ejs from 'ejs';
|
|
8
|
+
// Simple case converters
|
|
9
|
+
function paramCase(str) {
|
|
10
|
+
return str.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
11
|
+
.replace(/[\s_]+/g, '-')
|
|
12
|
+
.toLowerCase();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function pascalCase(str) {
|
|
16
|
+
return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '')
|
|
17
|
+
.replace(/^(.)/, (_, c) => c.toUpperCase());
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = path.dirname(__filename);
|
|
22
|
+
|
|
23
|
+
const program = new Command();
|
|
24
|
+
|
|
25
|
+
program
|
|
26
|
+
.name('leedstack')
|
|
27
|
+
.version('3.1.0')
|
|
28
|
+
.description('Generate a full-stack web application with your choice of tech stack')
|
|
29
|
+
.argument('<appName>', 'Name of the application')
|
|
30
|
+
.requiredOption('-f, --frontend <type>', 'Frontend stack (react|vue|nextjs|svelte|angular)')
|
|
31
|
+
.requiredOption('-b, --backend <type>', 'Backend stack (node-express|python-fastapi|java-spring|go-echo)')
|
|
32
|
+
.requiredOption('--db <type>', 'Database (postgres|mysql|mongodb)')
|
|
33
|
+
.option('--auth <type>', 'Authentication (auth0|none)', 'none')
|
|
34
|
+
.option('--payments <type>', 'Payments (stripe|none)', 'none')
|
|
35
|
+
.addHelpText('after', `
|
|
36
|
+
Examples:
|
|
37
|
+
$ npx leedstack my-app --frontend react --backend python-fastapi --db postgres --auth auth0 --payments stripe contact admin
|
|
38
|
+
$ npx leedstack blog --frontend vue --backend node-express --db mongodb
|
|
39
|
+
$ npx leedstack shop --frontend nextjs --backend java-spring --db mysql --payments stripe
|
|
40
|
+
|
|
41
|
+
Supported Stacks:
|
|
42
|
+
Frontends: react (Vite + React 19), vue (Vue 3.5), nextjs (Next.js 15), svelte (SvelteKit 5.x), angular (Angular 19+)
|
|
43
|
+
Backends: node-express (Node 20+ + TypeScript), python-fastapi (Python 3.11+ FastAPI), java-spring (Java 21 + Maven), go-echo (Go 1.22+)
|
|
44
|
+
Databases: postgres (16+), mysql (8+), mongodb (7+)
|
|
45
|
+
|
|
46
|
+
Optional Modules:
|
|
47
|
+
--auth auth0 Auth0 OIDC authentication with JWT validation
|
|
48
|
+
--payments stripe Stripe Checkout + webhook integration
|
|
49
|
+
contact Contact form with database persistence
|
|
50
|
+
admin Protected admin dashboard (requires auth with 'admin' scope)
|
|
51
|
+
chatbot AI chatbot (works out-of-box, upgrades with OpenAI/Anthropic API key)
|
|
52
|
+
|
|
53
|
+
For more info: https://github.com/CTLeed/leedstack
|
|
54
|
+
`)
|
|
55
|
+
.action(async (appName, options, command) => {
|
|
56
|
+
const additionalModules = command.args.slice(1);
|
|
57
|
+
|
|
58
|
+
const validFrontends = ['react', 'vue', 'nextjs', 'svelte', 'angular'];
|
|
59
|
+
const validBackends = ['node-express', 'python-fastapi', 'java-spring', 'go-echo'];
|
|
60
|
+
const validDatabases = ['postgres', 'mysql', 'mongodb'];
|
|
61
|
+
const validAuth = ['auth0', 'none'];
|
|
62
|
+
const validPayments = ['stripe', 'none'];
|
|
63
|
+
const validModules = ['contact', 'admin', 'chatbot'];
|
|
64
|
+
|
|
65
|
+
// Validation
|
|
66
|
+
if (!validFrontends.includes(options.frontend)) {
|
|
67
|
+
console.error(`ā Invalid frontend: ${options.frontend}. Must be one of: ${validFrontends.join(', ')}`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
if (!validBackends.includes(options.backend)) {
|
|
71
|
+
console.error(`ā Invalid backend: ${options.backend}. Must be one of: ${validBackends.join(', ')}`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
if (!validDatabases.includes(options.db)) {
|
|
75
|
+
console.error(`ā Invalid database: ${options.db}. Must be one of: ${validDatabases.join(', ')}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
if (!validAuth.includes(options.auth)) {
|
|
79
|
+
console.error(`ā Invalid auth: ${options.auth}. Must be one of: ${validAuth.join(', ')}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
if (!validPayments.includes(options.payments)) {
|
|
83
|
+
console.error(`ā Invalid payments: ${options.payments}. Must be one of: ${validPayments.join(', ')}`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
for (const mod of additionalModules) {
|
|
87
|
+
if (!validModules.includes(mod)) {
|
|
88
|
+
console.error(`ā Invalid module: ${mod}. Must be one of: ${validModules.join(', ')}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const targetDir = path.resolve(process.cwd(), appName);
|
|
94
|
+
|
|
95
|
+
// Fail if target directory exists
|
|
96
|
+
if (await fs.pathExists(targetDir)) {
|
|
97
|
+
console.error(`ā Directory ${appName} already exists. Please choose a different name or remove the existing directory.`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const appSlug = paramCase(appName);
|
|
102
|
+
const AppName = pascalCase(appName);
|
|
103
|
+
|
|
104
|
+
const modules = {
|
|
105
|
+
auth: options.auth !== 'none',
|
|
106
|
+
payments: options.payments !== 'none',
|
|
107
|
+
contact: additionalModules.includes('contact'),
|
|
108
|
+
admin: additionalModules.includes('admin'),
|
|
109
|
+
chatbot: additionalModules.includes('chatbot')
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const frontendPorts = {
|
|
113
|
+
react: 5173,
|
|
114
|
+
vue: 5173,
|
|
115
|
+
svelte: 5173,
|
|
116
|
+
nextjs: 3000,
|
|
117
|
+
angular: 4200
|
|
118
|
+
};
|
|
119
|
+
const frontendPort = frontendPorts[options.frontend] || 5173;
|
|
120
|
+
|
|
121
|
+
const context = {
|
|
122
|
+
appName,
|
|
123
|
+
appSlug,
|
|
124
|
+
AppName,
|
|
125
|
+
frontend: options.frontend,
|
|
126
|
+
backend: options.backend,
|
|
127
|
+
db: options.db,
|
|
128
|
+
auth: options.auth,
|
|
129
|
+
payments: options.payments,
|
|
130
|
+
modules,
|
|
131
|
+
frontendPort
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
console.log(`\nš Creating ${appName}...`);
|
|
135
|
+
console.log(` Frontend: ${options.frontend}`);
|
|
136
|
+
console.log(` Backend: ${options.backend}`);
|
|
137
|
+
console.log(` Database: ${options.db}`);
|
|
138
|
+
if (modules.auth) console.log(` Auth: ${options.auth}`);
|
|
139
|
+
if (modules.payments) console.log(` Payments: ${options.payments}`);
|
|
140
|
+
if (modules.contact) console.log(` ā contact module`);
|
|
141
|
+
if (modules.admin) console.log(` ā admin module`);
|
|
142
|
+
if (modules.chatbot) console.log(` ā chatbot module`);
|
|
143
|
+
console.log('');
|
|
144
|
+
|
|
145
|
+
const warnings = [];
|
|
146
|
+
const templatesDir = path.resolve(__dirname, '..', 'tools', 'templates');
|
|
147
|
+
|
|
148
|
+
// Template resolution order
|
|
149
|
+
const templateLayers = [
|
|
150
|
+
{ type: 'base', path: path.join(templatesDir, 'base') },
|
|
151
|
+
{ type: 'frontend', path: path.join(templatesDir, 'frontend', options.frontend) },
|
|
152
|
+
{ type: 'backend', path: path.join(templatesDir, 'backend', options.backend) },
|
|
153
|
+
{ type: 'db', path: path.join(templatesDir, 'db', options.db, 'backend', options.backend) }
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
// Add module layers
|
|
157
|
+
const moduleNames = [];
|
|
158
|
+
if (modules.auth) moduleNames.push('auth');
|
|
159
|
+
if (modules.payments) moduleNames.push('payments');
|
|
160
|
+
if (modules.contact) moduleNames.push('contact');
|
|
161
|
+
if (modules.admin) moduleNames.push('admin');
|
|
162
|
+
if (modules.chatbot) moduleNames.push('chatbot');
|
|
163
|
+
|
|
164
|
+
for (const modName of moduleNames) {
|
|
165
|
+
templateLayers.push({
|
|
166
|
+
type: `module-${modName}-frontend`,
|
|
167
|
+
path: path.join(templatesDir, 'modules', modName, 'frontend', options.frontend)
|
|
168
|
+
});
|
|
169
|
+
templateLayers.push({
|
|
170
|
+
type: `module-${modName}-backend`,
|
|
171
|
+
path: path.join(templatesDir, 'modules', modName, 'backend', options.backend)
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Process each layer
|
|
176
|
+
for (const layer of templateLayers) {
|
|
177
|
+
if (await fs.pathExists(layer.path)) {
|
|
178
|
+
await copyAndRenderTemplates(layer.path, targetDir, context);
|
|
179
|
+
} else {
|
|
180
|
+
const skipDbWarning = layer.type === 'db' && ['go-echo', 'python-fastapi'].includes(options.backend);
|
|
181
|
+
if (!skipDbWarning) {
|
|
182
|
+
warnings.push(`ā ${layer.type}: MISSING (path: ${layer.path})`);
|
|
183
|
+
|
|
184
|
+
// Generate stub
|
|
185
|
+
const stubPath = path.join(targetDir, `MISSING_${layer.type}.txt`);
|
|
186
|
+
await fs.writeFile(stubPath, `TODO: Missing template\nPath: ${layer.type}\nContract: Implement the documented endpoint/component signature.\n`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log(`ā
Project ${appName} created successfully!\n`);
|
|
192
|
+
|
|
193
|
+
if (warnings.length > 0) {
|
|
194
|
+
console.log('ā ļø Warnings:');
|
|
195
|
+
warnings.forEach(w => console.log(` ${w}`));
|
|
196
|
+
console.log('');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Print next steps
|
|
200
|
+
console.log('š Next steps:\n');
|
|
201
|
+
console.log('1. Start the database:');
|
|
202
|
+
console.log(` cd ${appName}`);
|
|
203
|
+
console.log(' docker compose up -d\n');
|
|
204
|
+
|
|
205
|
+
console.log('2. Set up environment variables:');
|
|
206
|
+
console.log(' cp .env.example .env');
|
|
207
|
+
console.log(' # Edit .env with your configuration\n');
|
|
208
|
+
|
|
209
|
+
console.log('3. Start the backend:');
|
|
210
|
+
console.log(' cd backend');
|
|
211
|
+
if (options.backend === 'java-spring') {
|
|
212
|
+
console.log(' mvn spring-boot:run');
|
|
213
|
+
} else if (options.backend === 'node-express') {
|
|
214
|
+
console.log(' npm install');
|
|
215
|
+
console.log(' npm run dev');
|
|
216
|
+
} else if (options.backend === 'go-echo') {
|
|
217
|
+
console.log(' go run ./cmd/server');
|
|
218
|
+
}
|
|
219
|
+
console.log('');
|
|
220
|
+
|
|
221
|
+
console.log('4. Start the frontend (in a new terminal):');
|
|
222
|
+
console.log(' cd frontend');
|
|
223
|
+
console.log(' cp .env.example .env');
|
|
224
|
+
console.log(' npm install');
|
|
225
|
+
console.log(' npm run dev');
|
|
226
|
+
console.log('');
|
|
227
|
+
|
|
228
|
+
if (modules.auth) {
|
|
229
|
+
console.log('5. Configure Auth0:');
|
|
230
|
+
console.log(' - Create a SPA app in Auth0');
|
|
231
|
+
console.log(' - Set Allowed Callback URLs: http://localhost:5173/callback');
|
|
232
|
+
console.log(' - Set Allowed Logout URLs: http://localhost:5173');
|
|
233
|
+
console.log(' - Set Allowed Web Origins: http://localhost:5173');
|
|
234
|
+
console.log(` - Create an API with Identifier: https://api.${appSlug}`);
|
|
235
|
+
console.log(' - Update .env files with Auth0 credentials\n');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (modules.payments) {
|
|
239
|
+
console.log(`${modules.auth ? '6' : '5'}. Configure Stripe:`);
|
|
240
|
+
console.log(' - Set STRIPE_SECRET_KEY in backend .env');
|
|
241
|
+
console.log(' - Run: stripe listen --forward-to localhost:8080/stripe/webhook');
|
|
242
|
+
console.log(' - Set STRIPE_WEBHOOK_SECRET from the output\n');
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
async function copyAndRenderTemplates(srcDir, destDir, context) {
|
|
247
|
+
const items = await fs.readdir(srcDir, { withFileTypes: true });
|
|
248
|
+
|
|
249
|
+
for (const item of items) {
|
|
250
|
+
const srcPath = path.join(srcDir, item.name);
|
|
251
|
+
let destName = item.name;
|
|
252
|
+
|
|
253
|
+
// Remove .ejs extension if present
|
|
254
|
+
if (destName.endsWith('.ejs')) {
|
|
255
|
+
destName = destName.slice(0, -4);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const destPath = path.join(destDir, destName);
|
|
259
|
+
|
|
260
|
+
if (item.isDirectory()) {
|
|
261
|
+
await fs.ensureDir(destPath);
|
|
262
|
+
await copyAndRenderTemplates(srcPath, destPath, context);
|
|
263
|
+
} else {
|
|
264
|
+
// Check if it's an EJS template
|
|
265
|
+
if (item.name.endsWith('.ejs')) {
|
|
266
|
+
const template = await fs.readFile(srcPath, 'utf-8');
|
|
267
|
+
const rendered = ejs.render(template, context);
|
|
268
|
+
await fs.writeFile(destPath, rendered);
|
|
269
|
+
} else {
|
|
270
|
+
// Copy as-is
|
|
271
|
+
await fs.copy(srcPath, destPath);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
program.parse();
|