launchbase 1.0.4 → 1.0.6

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/bin/launchbase.js CHANGED
@@ -1,14 +1,46 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const path = require('path');
4
+ const net = require('net');
4
5
  const { Command } = require('commander');
5
6
  const crypto = require('crypto');
6
7
  const fs = require('fs-extra');
7
8
  const { execSync, spawn } = require('child_process');
8
9
 
9
- const VERSION = '1.0.4';
10
+ const VERSION = '1.0.6';
10
11
  const program = new Command();
11
12
 
13
+ function findAvailablePort(startPort = 5432, maxAttempts = 100) {
14
+ return new Promise((resolve, reject) => {
15
+ const tryPort = (port, attempts) => {
16
+ if (attempts >= maxAttempts) {
17
+ reject(new Error('Could not find available port'));
18
+ return;
19
+ }
20
+
21
+ const server = net.createServer();
22
+ server.once('error', () => {
23
+ tryPort(port + 1, attempts + 1);
24
+ });
25
+ server.once('listening', () => {
26
+ server.close();
27
+ resolve(port);
28
+ });
29
+ server.listen(port);
30
+ };
31
+
32
+ tryPort(startPort, 0);
33
+ });
34
+ }
35
+
36
+ function findAvailablePorts() {
37
+ return Promise.all([
38
+ findAvailablePort(5432, 100), // Database port
39
+ findAvailablePort(3000, 100), // API port
40
+ findAvailablePort(5173, 100), // Frontend port
41
+ ]);
42
+ }
43
+
12
44
  function replaceInFile(filePath, replacements) {
13
45
  let content = fs.readFileSync(filePath, 'utf8');
14
46
  for (const [from, to] of Object.entries(replacements)) {
@@ -281,34 +313,34 @@ async function startDatabase(projectDir) {
281
313
  }
282
314
 
283
315
  try {
284
- execSync('docker compose up -d', {
316
+ // Stop any existing containers first to avoid port conflicts
317
+ try {
318
+ execSync('docker compose down', {
319
+ cwd: projectDir,
320
+ stdio: 'pipe'
321
+ });
322
+ } catch {}
323
+
324
+ execSync('docker compose up -d --wait', {
285
325
  cwd: projectDir,
286
326
  stdio: 'inherit'
287
327
  });
288
328
 
289
- console.log('\n Waiting for database to be ready...');
329
+ console.log('\n Database is ready!\n');
330
+ return true;
331
+ } catch (error) {
332
+ console.log('\n❌ Failed to start database.');
290
333
 
291
- // Wait for database to be ready
292
- let retries = 30;
293
- while (retries > 0) {
294
- try {
295
- execSync('docker compose exec -T db pg_isready -U postgres', {
296
- cwd: projectDir,
297
- stdio: 'pipe'
298
- });
299
- console.log('✅ Database is ready!\n');
300
- return true;
301
- } catch {
302
- process.stdout.write('.');
303
- await new Promise(r => setTimeout(r, 1000));
304
- retries--;
305
- }
334
+ // Check for port conflict
335
+ if (error.message && error.message.includes('port is already allocated')) {
336
+ console.log(' Port conflict detected. Another PostgreSQL may be running.\n');
337
+ console.log('💡 Solutions:');
338
+ console.log(' 1. Stop the other PostgreSQL: docker stop <container>');
339
+ console.log(' 2. Or change the port in docker-compose.yml\n');
340
+ } else {
341
+ console.log(' Check Docker logs: docker compose logs\n');
306
342
  }
307
343
 
308
- console.log('\n⚠️ Database may not be ready yet. Check logs: docker compose logs\n');
309
- return true;
310
- } catch (error) {
311
- console.log('\n❌ Failed to start database. Check Docker logs.\n');
312
344
  return false;
313
345
  }
314
346
  }
@@ -331,6 +363,11 @@ program
331
363
  console.log('\n🚀 LaunchBase CLI v' + VERSION + '\n');
332
364
  console.log('📁 Creating project:', appName);
333
365
 
366
+ // Find available ports first
367
+ console.log('🔍 Finding available ports...');
368
+ const [dbPort, apiPort, frontendPort] = await findAvailablePorts();
369
+ console.log(` Database: ${dbPort}, API: ${apiPort}, Frontend: ${frontendPort}\n`);
370
+
334
371
  const templateDir = path.resolve(__dirname, '..', 'template');
335
372
  const targetDir = path.resolve(process.cwd(), appName);
336
373
 
@@ -380,13 +417,23 @@ program
380
417
  }
381
418
  });
382
419
 
383
- // Replace placeholders
420
+ // Replace placeholders with dynamic ports
384
421
  const replacements = {
385
422
  '__APP_NAME__': appName,
386
423
  '"name": "launchbase-template"': `"name": "${appName}"`,
424
+ '"PORT=3000"': `"PORT=${apiPort}"`,
425
+ 'PORT=3000': `PORT=${apiPort}`,
426
+ 'localhost:3000': `localhost:${apiPort}`,
427
+ 'localhost:5173': `localhost:${frontendPort}`,
428
+ 'localhost:5433': `localhost:${dbPort}`,
429
+ 'localhost:5432': `localhost:${dbPort}`,
430
+ '"5433:5432"': `"${dbPort}:5432"`,
431
+ '"5432:5432"': `"${dbPort}:5432"`,
432
+ '"3000:3000"': `"${apiPort}:3000"`,
387
433
  };
388
434
 
389
- const filesToReplace = ['package.json', '.env.example', 'README.md'];
435
+ // Update files with port replacements
436
+ const filesToReplace = ['package.json', '.env.example', 'README.md', 'docker-compose.yml', '.env'];
390
437
  for (const rel of filesToReplace) {
391
438
  const fp = path.join(targetDir, rel);
392
439
  if (await fs.pathExists(fp)) {
@@ -402,9 +449,21 @@ program
402
449
  let env = await fs.readFile(envExamplePath, 'utf8');
403
450
  env = env.replace('JWT_ACCESS_SECRET=__CHANGE_ME__', `JWT_ACCESS_SECRET=${randomSecret(32)}`);
404
451
  env = env.replace('JWT_REFRESH_SECRET=__CHANGE_ME__', `JWT_REFRESH_SECRET=${randomSecret(32)}`);
452
+ // Ensure ports are correct
453
+ env = env.replace(/PORT=\d+/, `PORT=${apiPort}`);
454
+ env = env.replace(/localhost:\d+.*__APP_NAME__/g, `localhost:${dbPort}/${appName}`);
405
455
  await fs.writeFile(envPath, env, 'utf8');
406
456
  }
407
457
 
458
+ // Update docker-compose.yml with correct ports
459
+ const dockerComposePath = path.join(targetDir, 'docker-compose.yml');
460
+ if (await fs.pathExists(dockerComposePath)) {
461
+ let compose = await fs.readFile(dockerComposePath, 'utf8');
462
+ compose = compose.replace(/"(\d+):5432"/, `"${dbPort}:5432"`);
463
+ compose = compose.replace(/"(\d+):3000"/, `"${apiPort}:3000"`);
464
+ await fs.writeFile(dockerComposePath, compose, 'utf8');
465
+ }
466
+
408
467
  console.log('✅ Project files created\n');
409
468
 
410
469
  // Install dependencies
@@ -420,6 +479,7 @@ program
420
479
  }
421
480
 
422
481
  // Setup Docker and database
482
+ let dbReady = false;
423
483
  if (!options.noDocker) {
424
484
  console.log('\n🐳 Setting up database...\n');
425
485
 
@@ -431,27 +491,34 @@ program
431
491
  // Run migrations
432
492
  console.log('📄 Running database migrations...\n');
433
493
  runCommand('npx prisma migrate dev --name init', { cwd: targetDir });
494
+ dbReady = true;
434
495
  }
435
496
  }
436
497
  }
437
498
 
499
+ // Generate Prisma client before starting dev server
500
+ console.log('📦 Generating Prisma client...\n');
501
+ runCommand('npx prisma generate', { cwd: targetDir });
502
+
438
503
  // Start dev server
439
504
  console.log('\n🚀 Starting development server...\n');
440
505
  console.log('━'.repeat(50));
441
- console.log(' API: http://localhost:3000');
442
- console.log(' Docs: http://localhost:3000/docs');
443
- console.log(' Health: http://localhost:3000/health');
506
+ console.log(` API: http://localhost:${apiPort}`);
507
+ console.log(` Docs: http://localhost:${apiPort}/docs`);
508
+ console.log(` Health: http://localhost:${apiPort}/health`);
444
509
  if (options.template) {
445
- console.log(' Frontend: http://localhost:5173');
510
+ console.log(` Frontend: http://localhost:${frontendPort}`);
446
511
  }
512
+ console.log(` Database: localhost:${dbPort}`);
447
513
  console.log('━'.repeat(50));
448
514
  console.log('\nPress Ctrl+C to stop\n');
449
515
 
450
- // Start backend
516
+ // Start backend with correct port
451
517
  const backend = spawn('npm', ['run', 'start:dev'], {
452
518
  cwd: targetDir,
453
519
  stdio: 'inherit',
454
- shell: true
520
+ shell: true,
521
+ env: { ...process.env, PORT: apiPort.toString() }
455
522
  });
456
523
 
457
524
  // Start frontend after delay if included
@@ -462,7 +529,8 @@ program
462
529
  const frontend = spawn('npm', ['run', 'dev'], {
463
530
  cwd: frontendPath,
464
531
  stdio: 'inherit',
465
- shell: true
532
+ shell: true,
533
+ env: { ...process.env, VITE_API_URL: `http://localhost:${apiPort}`, PORT: frontendPort.toString() }
466
534
  });
467
535
  }, 5000);
468
536
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "launchbase",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Generate production-ready NestJS backends with authentication, multi-tenancy, billing, and deployment in minutes",
5
5
  "author": "LaunchBase",
6
6
  "keywords": [
@@ -15,7 +15,7 @@ FRONTEND_URL=http://localhost:5173
15
15
  # Database
16
16
  # ===========================================
17
17
  # Local PostgreSQL (with Docker)
18
- DATABASE_URL=postgresql://postgres:postgres@localhost:5432/__APP_NAME__?schema=public
18
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/__APP_NAME__?schema=public
19
19
  # SQLite (for simple local dev)
20
20
  # DATABASE_URL="file:./dev.db"
21
21
 
@@ -7,9 +7,14 @@ services:
7
7
  POSTGRES_PASSWORD: postgres
8
8
  POSTGRES_DB: launchbase
9
9
  ports:
10
- - "5432:5432"
10
+ - "5433:5432"
11
11
  volumes:
12
12
  - db_data:/var/lib/postgresql/data
13
+ healthcheck:
14
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
15
+ interval: 5s
16
+ timeout: 5s
17
+ retries: 5
13
18
 
14
19
  api:
15
20
  build: .
@@ -21,7 +26,8 @@ services:
21
26
  ports:
22
27
  - "3000:3000"
23
28
  depends_on:
24
- - db
29
+ db:
30
+ condition: service_healthy
25
31
 
26
32
  volumes:
27
33
  db_data: