@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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +373 -0
  3. package/bin/saas.js +2 -0
  4. package/dist/chunk-26VE6QJ4.js +120 -0
  5. package/dist/chunk-26VE6QJ4.js.map +1 -0
  6. package/dist/chunk-3KD5CFV3.js +196 -0
  7. package/dist/chunk-3KD5CFV3.js.map +1 -0
  8. package/dist/chunk-5BCEXHNM.js +108 -0
  9. package/dist/chunk-5BCEXHNM.js.map +1 -0
  10. package/dist/chunk-N4OIAZSA.js +110 -0
  11. package/dist/chunk-N4OIAZSA.js.map +1 -0
  12. package/dist/chunk-ZD2ZSBK3.js +224 -0
  13. package/dist/chunk-ZD2ZSBK3.js.map +1 -0
  14. package/dist/dart-DXLFNGHR.js +41 -0
  15. package/dist/dart-DXLFNGHR.js.map +1 -0
  16. package/dist/drift-XYY4D366.js +59 -0
  17. package/dist/drift-XYY4D366.js.map +1 -0
  18. package/dist/flutter-J5BYPVIW.js +41 -0
  19. package/dist/flutter-J5BYPVIW.js.map +1 -0
  20. package/dist/freezed-QXFQ4GJC.js +58 -0
  21. package/dist/freezed-QXFQ4GJC.js.map +1 -0
  22. package/dist/gorouter-QBMTTFVR.js +56 -0
  23. package/dist/gorouter-QBMTTFVR.js.map +1 -0
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.js +1437 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/package-QO75XHBD.js +43 -0
  28. package/dist/package-QO75XHBD.js.map +1 -0
  29. package/dist/powersync-I3LR7TDN.js +37 -0
  30. package/dist/powersync-I3LR7TDN.js.map +1 -0
  31. package/dist/repository-BAOVD3NG.js +34 -0
  32. package/dist/repository-BAOVD3NG.js.map +1 -0
  33. package/dist/riverpod-XUU656PM.js +42 -0
  34. package/dist/riverpod-XUU656PM.js.map +1 -0
  35. package/dist/widget-YDKHPRXM.js +42 -0
  36. package/dist/widget-YDKHPRXM.js.map +1 -0
  37. package/package.json +89 -0
  38. package/templates/drift/dao.hbs +51 -0
  39. package/templates/drift/migration.hbs +15 -0
  40. package/templates/freezed/model.hbs +20 -0
  41. package/templates/gorouter/route.hbs +18 -0
  42. package/templates/powersync/rules.hbs +10 -0
  43. package/templates/powersync/schema.hbs +19 -0
  44. package/templates/repository/repository.hbs +62 -0
  45. package/templates/riverpod/async-notifier.hbs +44 -0
  46. package/templates/riverpod/family.hbs +9 -0
  47. package/templates/riverpod/future.hbs +9 -0
  48. package/templates/riverpod/notifier.hbs +34 -0
  49. 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
+ [![npm version](https://img.shields.io/npm/v/@beaulewis/saas-cli.svg?style=flat-square)](https://www.npmjs.com/package/@beaulewis/saas-cli)
10
+ [![license](https://img.shields.io/npm/l/@beaulewis/saas-cli.svg?style=flat-square)](https://github.com/Beaulewis1977/saas-cli/blob/main/LICENSE)
11
+ [![CI](https://img.shields.io/github/actions/workflow/status/Beaulewis1977/saas-cli/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/Beaulewis1977/saas-cli/actions/workflows/ci.yml)
12
+ [![node](https://img.shields.io/node/v/@beaulewis/saas-cli.svg?style=flat-square)](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
+ [![GitHub](https://img.shields.io/badge/GitHub-@Beaulewis1977-181717?style=flat-square&logo=github)](https://github.com/Beaulewis1977)
362
+ [![Email](https://img.shields.io/badge/Email-blewisxx@gmail.com-EA4335?style=flat-square&logo=gmail&logoColor=white)](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
+ [![Ko-fi](https://img.shields.io/badge/Ko--fi-Support%20Me-FF5E5B?style=for-the-badge&logo=ko-fi&logoColor=white)](https://ko-fi.com/beaulewis)
371
+ [![Venmo](https://img.shields.io/badge/Venmo-@BeauinTulsa-3D95CE?style=for-the-badge&logo=venmo&logoColor=white)](https://venmo.com/BeauinTulsa)
372
+
373
+ </div>
package/bin/saas.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/index.js';
@@ -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":[]}