@beaulewis/saas-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +373 -0
- package/bin/saas.js +2 -0
- package/dist/chunk-26VE6QJ4.js +120 -0
- package/dist/chunk-26VE6QJ4.js.map +1 -0
- package/dist/chunk-3KD5CFV3.js +196 -0
- package/dist/chunk-3KD5CFV3.js.map +1 -0
- package/dist/chunk-5BCEXHNM.js +108 -0
- package/dist/chunk-5BCEXHNM.js.map +1 -0
- package/dist/chunk-N4OIAZSA.js +110 -0
- package/dist/chunk-N4OIAZSA.js.map +1 -0
- package/dist/chunk-ZD2ZSBK3.js +224 -0
- package/dist/chunk-ZD2ZSBK3.js.map +1 -0
- package/dist/dart-DXLFNGHR.js +41 -0
- package/dist/dart-DXLFNGHR.js.map +1 -0
- package/dist/drift-XYY4D366.js +59 -0
- package/dist/drift-XYY4D366.js.map +1 -0
- package/dist/flutter-J5BYPVIW.js +41 -0
- package/dist/flutter-J5BYPVIW.js.map +1 -0
- package/dist/freezed-QXFQ4GJC.js +58 -0
- package/dist/freezed-QXFQ4GJC.js.map +1 -0
- package/dist/gorouter-QBMTTFVR.js +56 -0
- package/dist/gorouter-QBMTTFVR.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1437 -0
- package/dist/index.js.map +1 -0
- package/dist/package-QO75XHBD.js +43 -0
- package/dist/package-QO75XHBD.js.map +1 -0
- package/dist/powersync-I3LR7TDN.js +37 -0
- package/dist/powersync-I3LR7TDN.js.map +1 -0
- package/dist/repository-BAOVD3NG.js +34 -0
- package/dist/repository-BAOVD3NG.js.map +1 -0
- package/dist/riverpod-XUU656PM.js +42 -0
- package/dist/riverpod-XUU656PM.js.map +1 -0
- package/dist/widget-YDKHPRXM.js +42 -0
- package/dist/widget-YDKHPRXM.js.map +1 -0
- package/package.json +89 -0
- package/templates/drift/dao.hbs +51 -0
- package/templates/drift/migration.hbs +15 -0
- package/templates/freezed/model.hbs +20 -0
- package/templates/gorouter/route.hbs +18 -0
- package/templates/powersync/rules.hbs +10 -0
- package/templates/powersync/schema.hbs +19 -0
- package/templates/repository/repository.hbs +62 -0
- package/templates/riverpod/async-notifier.hbs +44 -0
- package/templates/riverpod/family.hbs +9 -0
- package/templates/riverpod/future.hbs +9 -0
- package/templates/riverpod/notifier.hbs +34 -0
- package/templates/riverpod/stream.hbs +9 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Beau Lewis
|
|
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,373 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# saas-cli
|
|
4
|
+
|
|
5
|
+
**A unified CLI for Flutter SaaS development**
|
|
6
|
+
|
|
7
|
+
Live documentation · AI-powered assistance · Code generation · Backend integrations
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@beaulewis/saas-cli)
|
|
10
|
+
[](https://github.com/Beaulewis1977/saas-cli/blob/main/LICENSE)
|
|
11
|
+
[](https://github.com/Beaulewis1977/saas-cli/actions/workflows/ci.yml)
|
|
12
|
+
[](https://nodejs.org)
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Table of Contents
|
|
19
|
+
|
|
20
|
+
- [Features](#features)
|
|
21
|
+
- [Installation](#installation)
|
|
22
|
+
- [Quick Start](#quick-start)
|
|
23
|
+
- [Commands](#commands)
|
|
24
|
+
- [docs](#docs---documentation-lookup)
|
|
25
|
+
- [ask](#ask---ai-powered-questions)
|
|
26
|
+
- [gen](#gen---code-generation)
|
|
27
|
+
- [supabase](#supabase---database-management)
|
|
28
|
+
- [redis](#redis---cache-management)
|
|
29
|
+
- [cf](#cf---cloudflare-workers)
|
|
30
|
+
- [push](#push---push-notifications)
|
|
31
|
+
- [flags](#flags---feature-flags)
|
|
32
|
+
- [video](#video---video-processing)
|
|
33
|
+
- [init](#init---project-scaffolding)
|
|
34
|
+
- [Environment Variables](#environment-variables)
|
|
35
|
+
- [Configuration](#configuration)
|
|
36
|
+
- [Security Notes](#security-notes)
|
|
37
|
+
- [Contributing](#contributing)
|
|
38
|
+
- [License](#license)
|
|
39
|
+
- [Author](#author)
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
|
|
45
|
+
| Feature | Description |
|
|
46
|
+
|---------|-------------|
|
|
47
|
+
| **Live Documentation** | Query Flutter, Dart, and package docs via Context7 |
|
|
48
|
+
| **AI-Powered Questions** | Ask technical questions with Perplexity AI |
|
|
49
|
+
| **Code Generation** | Generate Riverpod, Drift, GoRouter, Freezed, and more |
|
|
50
|
+
| **Supabase Management** | RLS policies, migrations, types, functions |
|
|
51
|
+
| **Backend Services** | Redis, Cloudflare Workers, OneSignal, PostHog |
|
|
52
|
+
| **Video Processing** | FFmpeg-based video operations |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Global install via npm
|
|
60
|
+
npm install -g @beaulewis/saas-cli
|
|
61
|
+
|
|
62
|
+
# Global install via pnpm
|
|
63
|
+
pnpm add -g @beaulewis/saas-cli
|
|
64
|
+
|
|
65
|
+
# Run without installing
|
|
66
|
+
npx @beaulewis/saas-cli --help
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Verify installation:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
saas --version
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
<details>
|
|
76
|
+
<summary><strong>Install from source</strong></summary>
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
git clone https://github.com/Beaulewis1977/saas-cli.git
|
|
80
|
+
cd saas-cli
|
|
81
|
+
pnpm install
|
|
82
|
+
pnpm build
|
|
83
|
+
pnpm link --global
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
</details>
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Quick Start
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Look up Flutter documentation
|
|
94
|
+
saas docs flutter "ListView.builder with pagination"
|
|
95
|
+
|
|
96
|
+
# Ask AI a question
|
|
97
|
+
saas ask "best practices for offline-first Flutter apps"
|
|
98
|
+
|
|
99
|
+
# Generate a Riverpod notifier
|
|
100
|
+
saas gen riverpod notifier UserList --state "List<User>"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Commands
|
|
106
|
+
|
|
107
|
+
### `docs` - Documentation Lookup
|
|
108
|
+
|
|
109
|
+
Query live documentation via Context7.
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
saas docs flutter "widget lifecycle"
|
|
113
|
+
saas docs dart "async streams"
|
|
114
|
+
saas docs package riverpod "provider family"
|
|
115
|
+
saas docs widget "TextField decoration"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `ask` - AI-Powered Questions
|
|
119
|
+
|
|
120
|
+
Ask technical questions using Perplexity AI.
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
saas ask "how to implement pull-to-refresh in Flutter"
|
|
124
|
+
saas ask --model sonar-pro "explain Flutter rendering pipeline"
|
|
125
|
+
saas ask --model sonar-reasoning "debug this riverpod error"
|
|
126
|
+
saas ask --model sonar-deep-research "compare state management solutions"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Available Models:**
|
|
130
|
+
|
|
131
|
+
| Model | Use Case |
|
|
132
|
+
|-------|----------|
|
|
133
|
+
| `sonar` | Fast, general queries (default) |
|
|
134
|
+
| `sonar-pro` | Enhanced responses |
|
|
135
|
+
| `sonar-reasoning` | Complex problem solving |
|
|
136
|
+
| `sonar-deep-research` | In-depth research |
|
|
137
|
+
|
|
138
|
+
### `gen` - Code Generation
|
|
139
|
+
|
|
140
|
+
Generate Flutter code scaffolds.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Riverpod
|
|
144
|
+
saas gen riverpod notifier UserList --state "List<User>"
|
|
145
|
+
saas gen riverpod provider AuthService --async
|
|
146
|
+
saas gen riverpod family UserProfile --param userId
|
|
147
|
+
|
|
148
|
+
# Drift (SQLite)
|
|
149
|
+
saas gen drift table users --columns "id:int,name:text,email:text"
|
|
150
|
+
saas gen drift dao Users --table users
|
|
151
|
+
|
|
152
|
+
# GoRouter
|
|
153
|
+
saas gen gorouter route /profile --name profile
|
|
154
|
+
saas gen gorouter shell MainShell --routes home,profile,settings
|
|
155
|
+
|
|
156
|
+
# Freezed
|
|
157
|
+
saas gen freezed model User --fields "id:int,name:String,email:String?"
|
|
158
|
+
saas gen freezed union AuthState --variants loading,authenticated,unauthenticated
|
|
159
|
+
|
|
160
|
+
# PowerSync
|
|
161
|
+
saas gen powersync schema --from supabase
|
|
162
|
+
saas gen powersync sync-rules users,profiles
|
|
163
|
+
|
|
164
|
+
# Repository Pattern
|
|
165
|
+
saas gen repository User --methods "getById,getAll,create,update,delete"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### `supabase` - Database Management
|
|
169
|
+
|
|
170
|
+
Manage Supabase backend.
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
# View schema
|
|
174
|
+
saas supabase schema
|
|
175
|
+
saas supabase schema --table users
|
|
176
|
+
|
|
177
|
+
# Create table
|
|
178
|
+
saas supabase create-table profiles --columns "user_id:uuid,avatar:text,bio:text"
|
|
179
|
+
|
|
180
|
+
# RLS policies
|
|
181
|
+
saas supabase rls recipes --policy user-owned --column user_id
|
|
182
|
+
saas supabase rls posts --policy public-read
|
|
183
|
+
|
|
184
|
+
# Migrations
|
|
185
|
+
saas supabase migration "add_avatar_to_profiles" --sql "ALTER TABLE..."
|
|
186
|
+
|
|
187
|
+
# Generate TypeScript types
|
|
188
|
+
saas supabase types
|
|
189
|
+
|
|
190
|
+
# Database functions
|
|
191
|
+
saas supabase fn get_user_stats --returns json
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### `redis` - Cache Management
|
|
195
|
+
|
|
196
|
+
Manage Redis cache and queues.
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
saas redis ping
|
|
200
|
+
saas redis info
|
|
201
|
+
saas redis keys "user:*"
|
|
202
|
+
saas redis get "session:abc123"
|
|
203
|
+
saas redis set "cache:data" '{"key":"value"}' --ttl 3600
|
|
204
|
+
saas redis del "cache:data"
|
|
205
|
+
saas redis queue add jobs '{"task":"process"}'
|
|
206
|
+
saas redis queue pop jobs
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### `cf` - Cloudflare Workers
|
|
210
|
+
|
|
211
|
+
Manage Cloudflare Workers and KV.
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
# Workers
|
|
215
|
+
saas cf worker list
|
|
216
|
+
saas cf worker deploy ./worker.js --name my-worker
|
|
217
|
+
saas cf worker logs my-worker
|
|
218
|
+
|
|
219
|
+
# KV
|
|
220
|
+
saas cf kv list
|
|
221
|
+
saas cf kv get MY_NAMESPACE key123
|
|
222
|
+
saas cf kv put MY_NAMESPACE key123 "value"
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### `push` - Push Notifications
|
|
226
|
+
|
|
227
|
+
Send notifications via OneSignal.
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
saas push send --title "Hello" --message "World" --segments "All"
|
|
231
|
+
saas push schedule --title "Reminder" --time "2025-01-15T10:00:00Z"
|
|
232
|
+
saas push template list
|
|
233
|
+
saas push template create welcome --title "Welcome!" --message "Thanks for joining"
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### `flags` - Feature Flags
|
|
237
|
+
|
|
238
|
+
Manage feature flags via PostHog.
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
saas flags list
|
|
242
|
+
saas flags get dark-mode
|
|
243
|
+
saas flags set dark-mode --enabled --percent 50
|
|
244
|
+
saas flags add-user dark-mode user123
|
|
245
|
+
saas flags remove-user dark-mode user123
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### `video` - Video Processing
|
|
249
|
+
|
|
250
|
+
FFmpeg-based video operations.
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
saas video info input.mp4
|
|
254
|
+
saas video thumbnail input.mp4 --time 00:00:05 --output thumb.jpg
|
|
255
|
+
saas video resize input.mp4 --width 1280 --height 720
|
|
256
|
+
saas video compress input.mp4 --quality medium
|
|
257
|
+
saas video trim input.mp4 --start 00:00:10 --end 00:00:30
|
|
258
|
+
saas video combine video1.mp4 video2.mp4 --output merged.mp4
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### `init` - Project Scaffolding
|
|
262
|
+
|
|
263
|
+
Initialize new projects.
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
saas init flutter my-app --template saas
|
|
267
|
+
saas init supabase --project my-app
|
|
268
|
+
saas init worker my-edge-function
|
|
269
|
+
saas init add riverpod,drift,freezed
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Environment Variables
|
|
275
|
+
|
|
276
|
+
Create a `.env` file in your project root or set these in your shell configuration.
|
|
277
|
+
|
|
278
|
+
| Variable | Description | Required For |
|
|
279
|
+
|----------|-------------|--------------|
|
|
280
|
+
| `CONTEXT7_API_KEY` | Context7 API key | `docs` |
|
|
281
|
+
| `PERPLEXITY_API_KEY` | Perplexity API key | `ask` |
|
|
282
|
+
| `SUPABASE_PROJECT_REF` | Supabase project reference | `supabase` |
|
|
283
|
+
| `SUPABASE_ACCESS_TOKEN` | Supabase access token | `supabase` |
|
|
284
|
+
| `REDIS_URL` | Redis connection URL | `redis` |
|
|
285
|
+
| `CF_API_TOKEN` | Cloudflare API token | `cf` |
|
|
286
|
+
| `ONESIGNAL_APP_ID` | OneSignal app ID | `push` |
|
|
287
|
+
| `ONESIGNAL_API_KEY` | OneSignal API key | `push` |
|
|
288
|
+
| `POSTHOG_API_KEY` | PostHog API key | `flags` |
|
|
289
|
+
| `POSTHOG_PROJECT_ID` | PostHog project ID | `flags` |
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Global Options
|
|
294
|
+
|
|
295
|
+
```
|
|
296
|
+
--json Output results as JSON
|
|
297
|
+
-v, --verbose Enable verbose output
|
|
298
|
+
--debug Enable debug output
|
|
299
|
+
-V, --version Display version number
|
|
300
|
+
-h, --help Display help
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Configuration
|
|
306
|
+
|
|
307
|
+
The CLI stores configuration in `~/.config/saas-cli/`:
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
~/.config/saas-cli/
|
|
311
|
+
├── config.yaml # CLI settings
|
|
312
|
+
└── cache/ # Response cache for faster lookups
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Security Notes
|
|
318
|
+
|
|
319
|
+
This CLI executes external tools (FFmpeg, Wrangler, Flutter, Supabase CLI) via shell commands. Ensure you trust the input you provide, especially for:
|
|
320
|
+
|
|
321
|
+
- Project names in `init` commands
|
|
322
|
+
- File paths in `video` commands
|
|
323
|
+
- Custom arguments passed to backend CLIs
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Contributing
|
|
328
|
+
|
|
329
|
+
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for development guidelines.
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
# Clone and install
|
|
333
|
+
git clone https://github.com/Beaulewis1977/saas-cli.git
|
|
334
|
+
cd saas-cli
|
|
335
|
+
pnpm install
|
|
336
|
+
|
|
337
|
+
# Run in development
|
|
338
|
+
pnpm dev
|
|
339
|
+
|
|
340
|
+
# Run tests
|
|
341
|
+
pnpm test
|
|
342
|
+
|
|
343
|
+
# Build
|
|
344
|
+
pnpm build
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## License
|
|
350
|
+
|
|
351
|
+
[MIT License](LICENSE) - see LICENSE file for details.
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
<div align="center">
|
|
356
|
+
|
|
357
|
+
## Author
|
|
358
|
+
|
|
359
|
+
**Beau Lewis**
|
|
360
|
+
|
|
361
|
+
[](https://github.com/Beaulewis1977)
|
|
362
|
+
[](mailto:blewisxx@gmail.com)
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
### Support This Project
|
|
367
|
+
|
|
368
|
+
If you find this tool useful and want to support continued development:
|
|
369
|
+
|
|
370
|
+
[](https://ko-fi.com/beaulewis)
|
|
371
|
+
[](https://venmo.com/BeauinTulsa)
|
|
372
|
+
|
|
373
|
+
</div>
|
package/bin/saas.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// src/services/template.ts
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { readdir, readFile } from "fs/promises";
|
|
4
|
+
import { dirname, join } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import Handlebars from "handlebars";
|
|
7
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
function getTemplatesDir() {
|
|
9
|
+
const devPath = join(__dirname, "..", "..", "templates");
|
|
10
|
+
if (existsSync(devPath)) {
|
|
11
|
+
return devPath;
|
|
12
|
+
}
|
|
13
|
+
const prodPath = join(__dirname, "..", "templates");
|
|
14
|
+
if (existsSync(prodPath)) {
|
|
15
|
+
return prodPath;
|
|
16
|
+
}
|
|
17
|
+
return devPath;
|
|
18
|
+
}
|
|
19
|
+
Handlebars.registerHelper("camelCase", (str) => {
|
|
20
|
+
return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toLowerCase());
|
|
21
|
+
});
|
|
22
|
+
Handlebars.registerHelper("pascalCase", (str) => {
|
|
23
|
+
return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toUpperCase());
|
|
24
|
+
});
|
|
25
|
+
Handlebars.registerHelper("snakeCase", (str) => {
|
|
26
|
+
return str.replace(/([A-Z])/g, "_$1").replace(/[-\s]+/g, "_").toLowerCase().replace(/^_/, "");
|
|
27
|
+
});
|
|
28
|
+
Handlebars.registerHelper("kebabCase", (str) => {
|
|
29
|
+
return str.replace(/([A-Z])/g, "-$1").replace(/[_\s]+/g, "-").toLowerCase().replace(/^-/, "");
|
|
30
|
+
});
|
|
31
|
+
Handlebars.registerHelper("upperCase", (str) => str.toUpperCase());
|
|
32
|
+
Handlebars.registerHelper("lowerCase", (str) => str.toLowerCase());
|
|
33
|
+
Handlebars.registerHelper("singularize", (str) => {
|
|
34
|
+
if (str.endsWith("ies")) return str.slice(0, -3) + "y";
|
|
35
|
+
if (str.endsWith("es")) return str.slice(0, -2);
|
|
36
|
+
if (str.endsWith("s")) return str.slice(0, -1);
|
|
37
|
+
return str;
|
|
38
|
+
});
|
|
39
|
+
Handlebars.registerHelper("pluralize", (str) => {
|
|
40
|
+
if (str.endsWith("y")) return str.slice(0, -1) + "ies";
|
|
41
|
+
if (str.endsWith("s") || str.endsWith("x") || str.endsWith("ch") || str.endsWith("sh")) {
|
|
42
|
+
return str + "es";
|
|
43
|
+
}
|
|
44
|
+
return str + "s";
|
|
45
|
+
});
|
|
46
|
+
Handlebars.registerHelper("eq", (a, b) => a === b);
|
|
47
|
+
Handlebars.registerHelper("ne", (a, b) => a !== b);
|
|
48
|
+
Handlebars.registerHelper("or", (a, b) => a || b);
|
|
49
|
+
Handlebars.registerHelper("and", (a, b) => a && b);
|
|
50
|
+
Handlebars.registerHelper("now", () => (/* @__PURE__ */ new Date()).toISOString().split("T")[0]);
|
|
51
|
+
Handlebars.registerHelper("dartType", (sqlType) => {
|
|
52
|
+
const typeMap = {
|
|
53
|
+
int: "int",
|
|
54
|
+
integer: "int",
|
|
55
|
+
bigint: "int",
|
|
56
|
+
text: "String",
|
|
57
|
+
varchar: "String",
|
|
58
|
+
bool: "bool",
|
|
59
|
+
boolean: "bool",
|
|
60
|
+
real: "double",
|
|
61
|
+
double: "double",
|
|
62
|
+
float: "double",
|
|
63
|
+
blob: "Uint8List",
|
|
64
|
+
datetime: "DateTime",
|
|
65
|
+
timestamptz: "DateTime",
|
|
66
|
+
timestamp: "DateTime",
|
|
67
|
+
uuid: "String",
|
|
68
|
+
json: "Map<String, dynamic>",
|
|
69
|
+
jsonb: "Map<String, dynamic>"
|
|
70
|
+
};
|
|
71
|
+
return typeMap[sqlType.toLowerCase()] || "String";
|
|
72
|
+
});
|
|
73
|
+
Handlebars.registerHelper("driftColumn", (sqlType) => {
|
|
74
|
+
const columnMap = {
|
|
75
|
+
int: "integer",
|
|
76
|
+
integer: "integer",
|
|
77
|
+
bigint: "int64",
|
|
78
|
+
text: "text",
|
|
79
|
+
varchar: "text",
|
|
80
|
+
bool: "boolean",
|
|
81
|
+
boolean: "boolean",
|
|
82
|
+
real: "real",
|
|
83
|
+
double: "real",
|
|
84
|
+
float: "real",
|
|
85
|
+
blob: "blob",
|
|
86
|
+
datetime: "dateTime",
|
|
87
|
+
timestamptz: "dateTime",
|
|
88
|
+
timestamp: "dateTime",
|
|
89
|
+
uuid: "text",
|
|
90
|
+
json: "text",
|
|
91
|
+
jsonb: "text"
|
|
92
|
+
};
|
|
93
|
+
return columnMap[sqlType.toLowerCase()] || "text";
|
|
94
|
+
});
|
|
95
|
+
var templateCache = /* @__PURE__ */ new Map();
|
|
96
|
+
async function loadTemplate(category, name) {
|
|
97
|
+
const cacheKey = `${category}/${name}`;
|
|
98
|
+
if (templateCache.has(cacheKey)) {
|
|
99
|
+
return templateCache.get(cacheKey);
|
|
100
|
+
}
|
|
101
|
+
const templatesDir = getTemplatesDir();
|
|
102
|
+
const templatePath = join(templatesDir, category, `${name}.hbs`);
|
|
103
|
+
try {
|
|
104
|
+
const content = await readFile(templatePath, "utf-8");
|
|
105
|
+
const compiled = Handlebars.compile(content);
|
|
106
|
+
templateCache.set(cacheKey, compiled);
|
|
107
|
+
return compiled;
|
|
108
|
+
} catch (_error) {
|
|
109
|
+
throw new Error(`Template not found: ${category}/${name}.hbs`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function renderTemplate(category, name, context) {
|
|
113
|
+
const template = await loadTemplate(category, name);
|
|
114
|
+
return template(context);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export {
|
|
118
|
+
renderTemplate
|
|
119
|
+
};
|
|
120
|
+
//# sourceMappingURL=chunk-26VE6QJ4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/template.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readdir, readFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport Handlebars from 'handlebars';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n/**\n * Get the templates directory path\n */\nexport function getTemplatesDir(): string {\n // Try project root first (development)\n const devPath = join(__dirname, '..', '..', 'templates');\n if (existsSync(devPath)) {\n return devPath;\n }\n\n // Try dist location (production)\n const prodPath = join(__dirname, '..', 'templates');\n if (existsSync(prodPath)) {\n return prodPath;\n }\n\n return devPath;\n}\n\n// Register Handlebars helpers\nHandlebars.registerHelper('camelCase', (str: string) => {\n return str\n .replace(/[-_\\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))\n .replace(/^(.)/, (c) => c.toLowerCase());\n});\n\nHandlebars.registerHelper('pascalCase', (str: string) => {\n return str\n .replace(/[-_\\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))\n .replace(/^(.)/, (c) => c.toUpperCase());\n});\n\nHandlebars.registerHelper('snakeCase', (str: string) => {\n return str\n .replace(/([A-Z])/g, '_$1')\n .replace(/[-\\s]+/g, '_')\n .toLowerCase()\n .replace(/^_/, '');\n});\n\nHandlebars.registerHelper('kebabCase', (str: string) => {\n return str\n .replace(/([A-Z])/g, '-$1')\n .replace(/[_\\s]+/g, '-')\n .toLowerCase()\n .replace(/^-/, '');\n});\n\nHandlebars.registerHelper('upperCase', (str: string) => str.toUpperCase());\n\nHandlebars.registerHelper('lowerCase', (str: string) => str.toLowerCase());\n\nHandlebars.registerHelper('singularize', (str: string) => {\n // Simple singularization\n if (str.endsWith('ies')) return str.slice(0, -3) + 'y';\n if (str.endsWith('es')) return str.slice(0, -2);\n if (str.endsWith('s')) return str.slice(0, -1);\n return str;\n});\n\nHandlebars.registerHelper('pluralize', (str: string) => {\n // Simple pluralization\n if (str.endsWith('y')) return str.slice(0, -1) + 'ies';\n if (str.endsWith('s') || str.endsWith('x') || str.endsWith('ch') || str.endsWith('sh')) {\n return str + 'es';\n }\n return str + 's';\n});\n\nHandlebars.registerHelper('eq', (a: unknown, b: unknown) => a === b);\nHandlebars.registerHelper('ne', (a: unknown, b: unknown) => a !== b);\nHandlebars.registerHelper('or', (a: unknown, b: unknown) => a || b);\nHandlebars.registerHelper('and', (a: unknown, b: unknown) => a && b);\n\n// Date helper\nHandlebars.registerHelper('now', () => new Date().toISOString().split('T')[0]);\n\n// Type mapping helpers for Drift/Dart\nHandlebars.registerHelper('dartType', (sqlType: string) => {\n const typeMap: Record<string, string> = {\n int: 'int',\n integer: 'int',\n bigint: 'int',\n text: 'String',\n varchar: 'String',\n bool: 'bool',\n boolean: 'bool',\n real: 'double',\n double: 'double',\n float: 'double',\n blob: 'Uint8List',\n datetime: 'DateTime',\n timestamptz: 'DateTime',\n timestamp: 'DateTime',\n uuid: 'String',\n json: 'Map<String, dynamic>',\n jsonb: 'Map<String, dynamic>',\n };\n return typeMap[sqlType.toLowerCase()] || 'String';\n});\n\nHandlebars.registerHelper('driftColumn', (sqlType: string) => {\n const columnMap: Record<string, string> = {\n int: 'integer',\n integer: 'integer',\n bigint: 'int64',\n text: 'text',\n varchar: 'text',\n bool: 'boolean',\n boolean: 'boolean',\n real: 'real',\n double: 'real',\n float: 'real',\n blob: 'blob',\n datetime: 'dateTime',\n timestamptz: 'dateTime',\n timestamp: 'dateTime',\n uuid: 'text',\n json: 'text',\n jsonb: 'text',\n };\n return columnMap[sqlType.toLowerCase()] || 'text';\n});\n\n/**\n * Template cache for compiled templates\n */\nconst templateCache = new Map<string, HandlebarsTemplateDelegate>();\n\n/**\n * Load and compile a template\n */\nexport async function loadTemplate(\n category: string,\n name: string,\n): Promise<HandlebarsTemplateDelegate> {\n const cacheKey = `${category}/${name}`;\n\n if (templateCache.has(cacheKey)) {\n return templateCache.get(cacheKey)!;\n }\n\n const templatesDir = getTemplatesDir();\n const templatePath = join(templatesDir, category, `${name}.hbs`);\n\n try {\n const content = await readFile(templatePath, 'utf-8');\n const compiled = Handlebars.compile(content);\n templateCache.set(cacheKey, compiled);\n return compiled;\n } catch (_error) {\n throw new Error(`Template not found: ${category}/${name}.hbs`);\n }\n}\n\n/**\n * Render a template with context\n */\nexport async function renderTemplate(\n category: string,\n name: string,\n context: Record<string, unknown>,\n): Promise<string> {\n const template = await loadTemplate(category, name);\n return template(context);\n}\n\n/**\n * List available templates in a category\n */\nexport async function listTemplates(category: string): Promise<string[]> {\n const templatesDir = getTemplatesDir();\n const categoryDir = join(templatesDir, category);\n\n try {\n const files = await readdir(categoryDir);\n return files.filter((f) => f.endsWith('.hbs')).map((f) => f.replace('.hbs', ''));\n } catch {\n return [];\n }\n}\n\n/**\n * Render an inline template string\n */\nexport function renderInline(template: string, context: Record<string, unknown>): string {\n const compiled = Handlebars.compile(template);\n return compiled(context);\n}\n\nexport { Handlebars };\n"],"mappings":"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","names":[]}
|