@xbg.solutions/create-backend 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 (32) hide show
  1. package/bin/create-backend.js +3 -0
  2. package/lib/cli.d.ts +12 -0
  3. package/lib/cli.js +55 -0
  4. package/lib/cli.js.map +1 -0
  5. package/lib/commands/add-util.d.ts +9 -0
  6. package/lib/commands/add-util.js +119 -0
  7. package/lib/commands/add-util.js.map +1 -0
  8. package/lib/commands/init.d.ts +11 -0
  9. package/lib/commands/init.js +372 -0
  10. package/lib/commands/init.js.map +1 -0
  11. package/lib/commands/sync.d.ts +10 -0
  12. package/lib/commands/sync.js +161 -0
  13. package/lib/commands/sync.js.map +1 -0
  14. package/lib/utils-registry.d.ts +25 -0
  15. package/lib/utils-registry.js +187 -0
  16. package/lib/utils-registry.js.map +1 -0
  17. package/package.json +38 -0
  18. package/src/project-template/__examples__/README.md +559 -0
  19. package/src/project-template/__examples__/blog-platform.model.ts +528 -0
  20. package/src/project-template/__examples__/communications-usage.ts +175 -0
  21. package/src/project-template/__examples__/ecommerce-store.model.ts +1200 -0
  22. package/src/project-template/__examples__/saas-multi-tenant.model.ts +798 -0
  23. package/src/project-template/__examples__/user.model.ts +221 -0
  24. package/src/project-template/__scripts__/deploy.js +115 -0
  25. package/src/project-template/__scripts__/generate.js +122 -0
  26. package/src/project-template/__scripts__/setup.js +425 -0
  27. package/src/project-template/__scripts__/validate.js +325 -0
  28. package/src/project-template/firebase.json +32 -0
  29. package/src/project-template/firestore.rules +12 -0
  30. package/src/project-template/functions/jest.config.js +49 -0
  31. package/src/project-template/functions/src/index.ts +46 -0
  32. package/src/project-template/functions/tsconfig.json +38 -0
@@ -0,0 +1,425 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Interactive Setup Wizard
5
+ *
6
+ * Configures the backend boilerplate project for either:
7
+ * - Backend-only deployment (standalone Firebase Functions)
8
+ * - Mono-repo deployment (e.g. SvelteKit frontend + Firebase backend)
9
+ *
10
+ * Handles:
11
+ * - Project naming and Firebase project ID
12
+ * - Database mode (multi-DB recommended, single-DB supported)
13
+ * - Environment configuration (.env generation)
14
+ * - Feature flag selection
15
+ * - .firebaserc update
16
+ * - Dependency installation
17
+ */
18
+
19
+ const readline = require('readline');
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+ const { execSync } = require('child_process');
23
+
24
+ const colors = {
25
+ reset: '\x1b[0m',
26
+ bright: '\x1b[1m',
27
+ dim: '\x1b[2m',
28
+ green: '\x1b[32m',
29
+ blue: '\x1b[34m',
30
+ yellow: '\x1b[33m',
31
+ red: '\x1b[31m',
32
+ cyan: '\x1b[36m',
33
+ };
34
+
35
+ const rl = readline.createInterface({
36
+ input: process.stdin,
37
+ output: process.stdout,
38
+ });
39
+
40
+ function question(prompt) {
41
+ return new Promise((resolve) => {
42
+ rl.question(prompt, resolve);
43
+ });
44
+ }
45
+
46
+ function printBanner() {
47
+ console.log(`
48
+ ${colors.bright}${colors.blue}╔════════════════════════════════════════════════════════════╗
49
+ ║ ║
50
+ ║ Backend Boilerplate Setup Wizard ║
51
+ ║ ║
52
+ ╚════════════════════════════════════════════════════════════╝${colors.reset}
53
+
54
+ ${colors.bright}This wizard configures your backend project.${colors.reset}
55
+ ${colors.dim}Press Enter to accept default values shown in [brackets].${colors.reset}
56
+ `);
57
+ }
58
+
59
+ function printSection(title) {
60
+ console.log(`\n${colors.bright}${colors.cyan}── ${title} ${'─'.repeat(Math.max(0, 54 - title.length))}${colors.reset}\n`);
61
+ }
62
+
63
+ // ──────────────────────────────────────────────────────────
64
+ // Resolve paths relative to __scripts__/ location
65
+ // ──────────────────────────────────────────────────────────
66
+ const SCRIPTS_DIR = __dirname;
67
+ const REPO_ROOT = path.resolve(SCRIPTS_DIR, '..');
68
+
69
+ async function setup() {
70
+ printBanner();
71
+ const config = {};
72
+
73
+ // ── Project basics ──────────────────────────────────────
74
+ printSection('Project');
75
+
76
+ config.projectName = (await question(
77
+ `${colors.blue}Project name ${colors.dim}[my-backend-api]${colors.reset}: `
78
+ )).trim() || 'my-backend-api';
79
+
80
+ config.firebaseProject = (await question(
81
+ `${colors.blue}Firebase project ID ${colors.dim}(required)${colors.reset}: `
82
+ )).trim();
83
+
84
+ if (!config.firebaseProject) {
85
+ console.log(`\n${colors.red}Firebase project ID is required.${colors.reset}`);
86
+ console.log(`${colors.dim}Create one at https://console.firebase.google.com${colors.reset}\n`);
87
+ rl.close();
88
+ process.exit(1);
89
+ }
90
+
91
+ // ── Deployment mode ─────────────────────────────────────
92
+ printSection('Deployment Mode');
93
+
94
+ console.log(` ${colors.bright}1)${colors.reset} Backend-only ${colors.dim}(standalone Firebase Functions project)${colors.reset}`);
95
+ console.log(` ${colors.bright}2)${colors.reset} Mono-repo ${colors.dim}(frontend + backend in one repo, backend in functions/)${colors.reset}`);
96
+
97
+ const modeChoice = (await question(
98
+ `\n${colors.blue}Choose mode ${colors.dim}[1]${colors.reset}: `
99
+ )).trim() || '1';
100
+ config.isMonoRepo = modeChoice === '2';
101
+
102
+ if (config.isMonoRepo) {
103
+ console.log(`\n${colors.yellow}Mono-repo mode selected.${colors.reset}`);
104
+ console.log(`${colors.dim}The backend source lives in functions/src/ and deploys as Firebase Functions.`);
105
+ console.log(`In a mono-repo the project root has its own package.json and the frontend`);
106
+ console.log(`typically lives in frontend/ (or a similar directory).${colors.reset}`);
107
+ console.log(`\n${colors.dim}Note: .claude/skills/ should live at the mono-repo root, not inside functions/.${colors.reset}`);
108
+ }
109
+
110
+ // ── Environment ─────────────────────────────────────────
111
+ printSection('Environment');
112
+
113
+ const envChoice = (await question(
114
+ `${colors.blue}Environment ${colors.dim}[development]${colors.reset} (development/staging/production): `
115
+ )).trim() || 'development';
116
+ config.environment = envChoice;
117
+
118
+ config.port = (await question(
119
+ `${colors.blue}Port ${colors.dim}[5001]${colors.reset}: `
120
+ )).trim() || '5001';
121
+
122
+ // ── Database ────────────────────────────────────────────
123
+ printSection('Database');
124
+
125
+ console.log(` ${colors.bright}Multi-database${colors.reset} ${colors.dim}(recommended)${colors.reset}: Separate Firestore databases for`);
126
+ console.log(` different concerns (e.g. main data vs analytics). Each database`);
127
+ console.log(` has independent scaling and can be in different regions.`);
128
+ console.log('');
129
+ console.log(` ${colors.bright}Single-database${colors.reset}: Everything in Firestore's (default) database.`);
130
+ console.log(` Simpler to start but harder to separate later.\n`);
131
+
132
+ const dbChoice = (await question(
133
+ `${colors.blue}Use multi-database? ${colors.dim}[Y/n]${colors.reset}: `
134
+ )).trim().toLowerCase();
135
+ config.multiDB = dbChoice !== 'n';
136
+
137
+ if (config.multiDB) {
138
+ config.mainDatabaseId = (await question(
139
+ `${colors.blue}Main database ID ${colors.dim}[main]${colors.reset}: `
140
+ )).trim() || 'main';
141
+
142
+ config.analyticsDatabaseId = (await question(
143
+ `${colors.blue}Analytics database ID ${colors.dim}[analytics]${colors.reset}: `
144
+ )).trim() || 'analytics';
145
+ } else {
146
+ config.mainDatabaseId = '(default)';
147
+ config.analyticsDatabaseId = '(default)';
148
+ }
149
+
150
+ // ── API ─────────────────────────────────────────────────
151
+ printSection('API');
152
+
153
+ config.corsOrigins = (await question(
154
+ `${colors.blue}CORS origins ${colors.dim}[http://localhost:5173,http://localhost:3000]${colors.reset}: `
155
+ )).trim() || 'http://localhost:5173,http://localhost:3000';
156
+
157
+ config.apiBasePath = (await question(
158
+ `${colors.blue}API base path ${colors.dim}[/api/v1]${colors.reset}: `
159
+ )).trim() || '/api/v1';
160
+
161
+ // ── Features ────────────────────────────────────────────
162
+ printSection('Features');
163
+
164
+ config.enableAuth = (await question(` Authentication ${colors.dim}[Y/n]${colors.reset}: `)).trim().toLowerCase() !== 'n';
165
+ config.enableMultiTenant = (await question(` Multi-tenant ${colors.dim}[y/N]${colors.reset}: `)).trim().toLowerCase() === 'y';
166
+ config.enableFileUploads = (await question(` File uploads ${colors.dim}[Y/n]${colors.reset}: `)).trim().toLowerCase() !== 'n';
167
+ config.enableNotifications = (await question(` Notifications ${colors.dim}[Y/n]${colors.reset}: `)).trim().toLowerCase() !== 'n';
168
+ config.enableAnalytics = (await question(` Analytics ${colors.dim}[y/N]${colors.reset}: `)).trim().toLowerCase() === 'y';
169
+ config.enableRealtime = (await question(` Realtime (SSE/WebSocket) ${colors.dim}[Y/n]${colors.reset}: `)).trim().toLowerCase() !== 'n';
170
+
171
+ // ── Write files ─────────────────────────────────────────
172
+ printSection('Writing configuration');
173
+
174
+ writeEnvFile(config);
175
+ writeFirebaseRc(config);
176
+ updateFirebaseJson(config);
177
+
178
+ // ── Install dependencies ────────────────────────────────
179
+ const installDeps = (await question(
180
+ `\n${colors.blue}Install dependencies? ${colors.dim}[Y/n]${colors.reset}: `
181
+ )).trim().toLowerCase();
182
+
183
+ if (installDeps !== 'n') {
184
+ console.log(`\n${colors.dim}Installing dependencies...${colors.reset}`);
185
+ try {
186
+ execSync('npm install', {
187
+ cwd: path.join(REPO_ROOT, 'functions'),
188
+ stdio: 'inherit',
189
+ });
190
+ console.log(`${colors.green}Dependencies installed.${colors.reset}`);
191
+ } catch (error) {
192
+ console.error(`${colors.red}Failed to install dependencies. Run 'npm install' in functions/ manually.${colors.reset}`);
193
+ }
194
+ }
195
+
196
+ // ── Summary ─────────────────────────────────────────────
197
+ printSummary(config);
198
+
199
+ rl.close();
200
+ }
201
+
202
+ // ──────────────────────────────────────────────────────────
203
+ // File writers
204
+ // ──────────────────────────────────────────────────────────
205
+
206
+ function writeEnvFile(config) {
207
+ const envContent = `# ──────────────────────────────────────────────────────────
208
+ # Application
209
+ # ──────────────────────────────────────────────────────────
210
+ APP_NAME=${config.projectName}
211
+ APP_VERSION=1.0.0
212
+ NODE_ENV=${config.environment}
213
+ PORT=${config.port}
214
+
215
+ # ──────────────────────────────────────────────────────────
216
+ # Firebase
217
+ # ──────────────────────────────────────────────────────────
218
+ FIREBASE_PROJECT_ID=${config.firebaseProject}
219
+
220
+ # ──────────────────────────────────────────────────────────
221
+ # Database
222
+ # ──────────────────────────────────────────────────────────
223
+ MAIN_DATABASE_ID=${config.mainDatabaseId}
224
+ ANALYTICS_DATABASE_ID=${config.analyticsDatabaseId}
225
+ # DB_RETRY_ATTEMPTS=3
226
+ # DB_RETRY_DELAY=1000
227
+ # DB_TIMEOUT=10000
228
+ # DB_ENABLE_CACHE=true
229
+
230
+ # Uncomment for local development with emulator:
231
+ # FIRESTORE_EMULATOR_HOST=localhost:8080
232
+
233
+ # ──────────────────────────────────────────────────────────
234
+ # API
235
+ # ──────────────────────────────────────────────────────────
236
+ API_BASE_PATH=${config.apiBasePath}
237
+ CORS_ORIGINS=${config.corsOrigins}
238
+ REQUEST_SIZE_LIMIT=10mb
239
+ # ENABLE_SWAGGER=true
240
+
241
+ # ──────────────────────────────────────────────────────────
242
+ # Features
243
+ # ──────────────────────────────────────────────────────────
244
+ FEATURE_AUTHENTICATION=${config.enableAuth}
245
+ FEATURE_MULTI_TENANT=${config.enableMultiTenant}
246
+ FEATURE_FILE_UPLOADS=${config.enableFileUploads}
247
+ FEATURE_NOTIFICATIONS=${config.enableNotifications}
248
+ FEATURE_ANALYTICS=${config.enableAnalytics}
249
+ FEATURE_REALTIME=${config.enableRealtime}
250
+
251
+ # ──────────────────────────────────────────────────────────
252
+ # Rate Limiting
253
+ # ──────────────────────────────────────────────────────────
254
+ RATE_LIMIT_ENABLED=true
255
+ RATE_LIMIT_WINDOW_MS=900000
256
+ RATE_LIMIT_MAX=100
257
+
258
+ # ──────────────────────────────────────────────────────────
259
+ # Logging
260
+ # ──────────────────────────────────────────────────────────
261
+ LOG_LEVEL=info
262
+
263
+ # ──────────────────────────────────────────────────────────
264
+ # PII Encryption
265
+ # Required for AES-256-GCM PII encryption (hashValue/hashFields).
266
+ # Generate with: openssl rand -hex 32
267
+ # ──────────────────────────────────────────────────────────
268
+ PII_ENCRYPTION_KEY=
269
+
270
+ # ──────────────────────────────────────────────────────────
271
+ # JWT / Authentication
272
+ # ──────────────────────────────────────────────────────────
273
+ JWT_ISSUER=${config.projectName}
274
+ JWT_AUDIENCE=${config.projectName}-api
275
+ JWT_EXPIRES_IN=1h
276
+ JWT_REFRESH_EXPIRES_IN=7d
277
+ TOKEN_BLACKLIST_ENABLED=true
278
+ TOKEN_BLACKLIST_CLEANUP_INTERVAL=3600000
279
+ TOKEN_BLACKLIST_RETENTION_DAYS=30
280
+
281
+ # ──────────────────────────────────────────────────────────
282
+ # Integrations (uncomment and configure as needed)
283
+ # ──────────────────────────────────────────────────────────
284
+ # EMAIL_ENABLED=true
285
+ # EMAIL_PROVIDER=mailjet
286
+ # MAILJET_API_KEY=
287
+ # MAILJET_SECRET_KEY=
288
+
289
+ # SMS_ENABLED=true
290
+ # SMS_PROVIDER=twilio
291
+ # TWILIO_ACCOUNT_SID=
292
+ # TWILIO_AUTH_TOKEN=
293
+ # TWILIO_FROM_NUMBER=
294
+
295
+ # CRM_ENABLED=true
296
+ # CRM_PROVIDER=hubspot
297
+ # HUBSPOT_API_KEY=
298
+
299
+ # STRIPE_SECRET_KEY=
300
+ # STRIPE_WEBHOOK_SECRET=
301
+
302
+ # ──────────────────────────────────────────────────────────
303
+ # Cache (disabled by default)
304
+ # ──────────────────────────────────────────────────────────
305
+ # CACHE_ENABLED=false
306
+ # CACHE_DEFAULT_PROVIDER=memory
307
+ # CACHE_DEFAULT_TTL=300
308
+ # CACHE_NAMESPACE=v1
309
+ `;
310
+
311
+ const envPath = path.join(REPO_ROOT, 'functions', '.env');
312
+ fs.writeFileSync(envPath, envContent);
313
+ console.log(` ${colors.green}Created${colors.reset} functions/.env`);
314
+ }
315
+
316
+ function writeFirebaseRc(config) {
317
+ const firebaseRc = {
318
+ projects: {
319
+ default: config.firebaseProject,
320
+ },
321
+ };
322
+
323
+ const rcPath = path.join(REPO_ROOT, '.firebaserc');
324
+ fs.writeFileSync(rcPath, JSON.stringify(firebaseRc, null, 2) + '\n');
325
+ console.log(` ${colors.green}Updated${colors.reset} .firebaserc`);
326
+ }
327
+
328
+ function updateFirebaseJson(config) {
329
+ const firebaseJsonPath = path.join(REPO_ROOT, 'firebase.json');
330
+
331
+ let firebaseJson;
332
+ try {
333
+ firebaseJson = JSON.parse(fs.readFileSync(firebaseJsonPath, 'utf8'));
334
+ } catch {
335
+ // Create a fresh firebase.json if it doesn't exist or is invalid
336
+ firebaseJson = {};
337
+ }
338
+
339
+ firebaseJson.firestore = {
340
+ rules: 'firestore.rules',
341
+ };
342
+
343
+ firebaseJson.functions = {
344
+ source: 'functions',
345
+ runtime: 'nodejs22',
346
+ ignore: [
347
+ 'node_modules',
348
+ '.git',
349
+ 'firebase-debug.log',
350
+ 'firebase-debug.*.log',
351
+ ],
352
+ predeploy: [
353
+ 'npm --prefix "$RESOURCE_DIR" run lint',
354
+ 'npm --prefix "$RESOURCE_DIR" run build',
355
+ ],
356
+ };
357
+
358
+ firebaseJson.emulators = {
359
+ functions: { port: parseInt(config.port, 10) },
360
+ firestore: { port: 8080 },
361
+ ui: { enabled: true, port: 4000 },
362
+ singleProjectMode: true,
363
+ };
364
+
365
+ fs.writeFileSync(firebaseJsonPath, JSON.stringify(firebaseJson, null, 2) + '\n');
366
+ console.log(` ${colors.green}Updated${colors.reset} firebase.json`);
367
+ }
368
+
369
+ // ──────────────────────────────────────────────────────────
370
+ // Summary
371
+ // ──────────────────────────────────────────────────────────
372
+
373
+ function printSummary(config) {
374
+ const features = [
375
+ config.enableAuth && 'Authentication',
376
+ config.enableMultiTenant && 'Multi-tenant',
377
+ config.enableFileUploads && 'File uploads',
378
+ config.enableNotifications && 'Notifications',
379
+ config.enableAnalytics && 'Analytics',
380
+ config.enableRealtime && 'Realtime',
381
+ ].filter(Boolean);
382
+
383
+ console.log(`
384
+ ${colors.green}${colors.bright}Setup complete!${colors.reset}
385
+
386
+ ${colors.bright}Configuration:${colors.reset}
387
+ Project: ${config.projectName}
388
+ Firebase project: ${config.firebaseProject}
389
+ Environment: ${config.environment}
390
+ Mode: ${config.isMonoRepo ? 'Mono-repo' : 'Backend-only'}
391
+ Database: ${config.multiDB ? `Multi-DB (main: ${config.mainDatabaseId}, analytics: ${config.analyticsDatabaseId})` : 'Single (default)'}
392
+ Features: ${features.join(', ') || 'None'}
393
+
394
+ ${colors.bright}Next steps:${colors.reset}
395
+ 1. Review functions/.env and add integration keys as needed
396
+ 2. Define your data model in __examples__/ (see existing examples)
397
+ 3. Run ${colors.cyan}npm run generate __examples__/your-model.ts${colors.reset} to generate code
398
+ 4. Register generated controllers in functions/src/index.ts
399
+ 5. Run ${colors.cyan}npm run build${colors.reset} to compile TypeScript
400
+ 6. Run ${colors.cyan}npm start${colors.reset} for local development`);
401
+
402
+ if (config.isMonoRepo) {
403
+ console.log(`
404
+ ${colors.yellow}Mono-repo notes:${colors.reset}
405
+ - Ensure .claude/skills/ live at the mono-repo root (not inside functions/)
406
+ - The project root should have its own package.json
407
+ - The frontend typically lives in frontend/ (or similar)
408
+ - Backend source stays in functions/src/`);
409
+ }
410
+
411
+ console.log(`
412
+ ${colors.dim}Documentation: See __docs__/ for guides${colors.reset}
413
+ ${colors.dim}Examples: See __examples__/ for sample data models${colors.reset}
414
+ `);
415
+ }
416
+
417
+ // ──────────────────────────────────────────────────────────
418
+ // Run
419
+ // ──────────────────────────────────────────────────────────
420
+
421
+ setup().catch((error) => {
422
+ console.error(`\n${colors.red}Setup failed: ${error.message}${colors.reset}\n`);
423
+ rl.close();
424
+ process.exit(1);
425
+ });