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 +99 -31
- package/package.json +1 -1
- package/template/.env.example +1 -1
- package/template/docker-compose.yml +8 -2
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
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(
|
|
442
|
-
console.log(
|
|
443
|
-
console.log(
|
|
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(
|
|
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
package/template/.env.example
CHANGED
|
@@ -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:
|
|
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
|
-
- "
|
|
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
|
-
|
|
29
|
+
db:
|
|
30
|
+
condition: service_healthy
|
|
25
31
|
|
|
26
32
|
volumes:
|
|
27
33
|
db_data:
|