launchbase 1.0.5 ā 1.0.7
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 +96 -32
- package/package.json +1 -1
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.7';
|
|
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)) {
|
|
@@ -264,32 +296,34 @@ async function startDatabase(projectDir) {
|
|
|
264
296
|
|
|
265
297
|
console.log('š³ Starting database with Docker Compose...\n');
|
|
266
298
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const psOutput = execSync('docker compose ps -q', {
|
|
270
|
-
cwd: projectDir,
|
|
271
|
-
stdio: 'pipe',
|
|
272
|
-
encoding: 'utf8'
|
|
273
|
-
}).trim();
|
|
274
|
-
|
|
275
|
-
if (psOutput) {
|
|
276
|
-
console.log('ā
Database containers already running\n');
|
|
277
|
-
return true;
|
|
278
|
-
}
|
|
279
|
-
} catch {
|
|
280
|
-
// Containers not running, proceed to start
|
|
281
|
-
}
|
|
299
|
+
// Get project name from directory
|
|
300
|
+
const projectName = path.basename(projectDir);
|
|
282
301
|
|
|
283
302
|
try {
|
|
284
|
-
//
|
|
303
|
+
// First, stop any containers with this project name (from previous runs)
|
|
285
304
|
try {
|
|
286
|
-
execSync(
|
|
305
|
+
execSync(`docker compose -p ${projectName} down --remove-orphans`, {
|
|
287
306
|
cwd: projectDir,
|
|
288
307
|
stdio: 'pipe'
|
|
289
308
|
});
|
|
290
309
|
} catch {}
|
|
291
310
|
|
|
292
|
-
|
|
311
|
+
// Also try to remove any containers with similar names
|
|
312
|
+
try {
|
|
313
|
+
const containers = execSync('docker ps -a --format "{{.Names}}"', {
|
|
314
|
+
stdio: 'pipe',
|
|
315
|
+
encoding: 'utf8'
|
|
316
|
+
});
|
|
317
|
+
const containerList = containers.trim().split('\n').filter(c => c.includes(projectName));
|
|
318
|
+
for (const container of containerList) {
|
|
319
|
+
try {
|
|
320
|
+
execSync(`docker rm -f ${container}`, { stdio: 'pipe' });
|
|
321
|
+
} catch {}
|
|
322
|
+
}
|
|
323
|
+
} catch {}
|
|
324
|
+
|
|
325
|
+
// Start fresh containers with explicit project name
|
|
326
|
+
execSync(`docker compose -p ${projectName} up -d --wait --force-recreate`, {
|
|
293
327
|
cwd: projectDir,
|
|
294
328
|
stdio: 'inherit'
|
|
295
329
|
});
|
|
@@ -301,10 +335,10 @@ async function startDatabase(projectDir) {
|
|
|
301
335
|
|
|
302
336
|
// Check for port conflict
|
|
303
337
|
if (error.message && error.message.includes('port is already allocated')) {
|
|
304
|
-
console.log(' Port conflict detected. Another
|
|
305
|
-
console.log('š”
|
|
306
|
-
console.log('
|
|
307
|
-
console.log('
|
|
338
|
+
console.log(' Port conflict detected. Another service is using this port.\n');
|
|
339
|
+
console.log('š” Try stopping other databases:');
|
|
340
|
+
console.log(' docker ps # list running containers');
|
|
341
|
+
console.log(' docker stop <container_id> # stop the conflicting container\n');
|
|
308
342
|
} else {
|
|
309
343
|
console.log(' Check Docker logs: docker compose logs\n');
|
|
310
344
|
}
|
|
@@ -331,6 +365,11 @@ program
|
|
|
331
365
|
console.log('\nš LaunchBase CLI v' + VERSION + '\n');
|
|
332
366
|
console.log('š Creating project:', appName);
|
|
333
367
|
|
|
368
|
+
// Find available ports first
|
|
369
|
+
console.log('š Finding available ports...');
|
|
370
|
+
const [dbPort, apiPort, frontendPort] = await findAvailablePorts();
|
|
371
|
+
console.log(` Database: ${dbPort}, API: ${apiPort}, Frontend: ${frontendPort}\n`);
|
|
372
|
+
|
|
334
373
|
const templateDir = path.resolve(__dirname, '..', 'template');
|
|
335
374
|
const targetDir = path.resolve(process.cwd(), appName);
|
|
336
375
|
|
|
@@ -380,13 +419,23 @@ program
|
|
|
380
419
|
}
|
|
381
420
|
});
|
|
382
421
|
|
|
383
|
-
// Replace placeholders
|
|
422
|
+
// Replace placeholders with dynamic ports
|
|
384
423
|
const replacements = {
|
|
385
424
|
'__APP_NAME__': appName,
|
|
386
425
|
'"name": "launchbase-template"': `"name": "${appName}"`,
|
|
426
|
+
'"PORT=3000"': `"PORT=${apiPort}"`,
|
|
427
|
+
'PORT=3000': `PORT=${apiPort}`,
|
|
428
|
+
'localhost:3000': `localhost:${apiPort}`,
|
|
429
|
+
'localhost:5173': `localhost:${frontendPort}`,
|
|
430
|
+
'localhost:5433': `localhost:${dbPort}`,
|
|
431
|
+
'localhost:5432': `localhost:${dbPort}`,
|
|
432
|
+
'"5433:5432"': `"${dbPort}:5432"`,
|
|
433
|
+
'"5432:5432"': `"${dbPort}:5432"`,
|
|
434
|
+
'"3000:3000"': `"${apiPort}:3000"`,
|
|
387
435
|
};
|
|
388
436
|
|
|
389
|
-
|
|
437
|
+
// Update files with port replacements
|
|
438
|
+
const filesToReplace = ['package.json', '.env.example', 'README.md', 'docker-compose.yml', '.env'];
|
|
390
439
|
for (const rel of filesToReplace) {
|
|
391
440
|
const fp = path.join(targetDir, rel);
|
|
392
441
|
if (await fs.pathExists(fp)) {
|
|
@@ -402,9 +451,21 @@ program
|
|
|
402
451
|
let env = await fs.readFile(envExamplePath, 'utf8');
|
|
403
452
|
env = env.replace('JWT_ACCESS_SECRET=__CHANGE_ME__', `JWT_ACCESS_SECRET=${randomSecret(32)}`);
|
|
404
453
|
env = env.replace('JWT_REFRESH_SECRET=__CHANGE_ME__', `JWT_REFRESH_SECRET=${randomSecret(32)}`);
|
|
454
|
+
// Ensure ports are correct
|
|
455
|
+
env = env.replace(/PORT=\d+/, `PORT=${apiPort}`);
|
|
456
|
+
env = env.replace(/localhost:\d+.*__APP_NAME__/g, `localhost:${dbPort}/${appName}`);
|
|
405
457
|
await fs.writeFile(envPath, env, 'utf8');
|
|
406
458
|
}
|
|
407
459
|
|
|
460
|
+
// Update docker-compose.yml with correct ports
|
|
461
|
+
const dockerComposePath = path.join(targetDir, 'docker-compose.yml');
|
|
462
|
+
if (await fs.pathExists(dockerComposePath)) {
|
|
463
|
+
let compose = await fs.readFile(dockerComposePath, 'utf8');
|
|
464
|
+
compose = compose.replace(/"(\d+):5432"/, `"${dbPort}:5432"`);
|
|
465
|
+
compose = compose.replace(/"(\d+):3000"/, `"${apiPort}:3000"`);
|
|
466
|
+
await fs.writeFile(dockerComposePath, compose, 'utf8');
|
|
467
|
+
}
|
|
468
|
+
|
|
408
469
|
console.log('ā
Project files created\n');
|
|
409
470
|
|
|
410
471
|
// Install dependencies
|
|
@@ -444,20 +505,22 @@ program
|
|
|
444
505
|
// Start dev server
|
|
445
506
|
console.log('\nš Starting development server...\n');
|
|
446
507
|
console.log('ā'.repeat(50));
|
|
447
|
-
console.log(
|
|
448
|
-
console.log(
|
|
449
|
-
console.log(
|
|
508
|
+
console.log(` API: http://localhost:${apiPort}`);
|
|
509
|
+
console.log(` Docs: http://localhost:${apiPort}/docs`);
|
|
510
|
+
console.log(` Health: http://localhost:${apiPort}/health`);
|
|
450
511
|
if (options.template) {
|
|
451
|
-
console.log(
|
|
512
|
+
console.log(` Frontend: http://localhost:${frontendPort}`);
|
|
452
513
|
}
|
|
514
|
+
console.log(` Database: localhost:${dbPort}`);
|
|
453
515
|
console.log('ā'.repeat(50));
|
|
454
516
|
console.log('\nPress Ctrl+C to stop\n');
|
|
455
517
|
|
|
456
|
-
// Start backend
|
|
518
|
+
// Start backend with correct port
|
|
457
519
|
const backend = spawn('npm', ['run', 'start:dev'], {
|
|
458
520
|
cwd: targetDir,
|
|
459
521
|
stdio: 'inherit',
|
|
460
|
-
shell: true
|
|
522
|
+
shell: true,
|
|
523
|
+
env: { ...process.env, PORT: apiPort.toString() }
|
|
461
524
|
});
|
|
462
525
|
|
|
463
526
|
// Start frontend after delay if included
|
|
@@ -468,7 +531,8 @@ program
|
|
|
468
531
|
const frontend = spawn('npm', ['run', 'dev'], {
|
|
469
532
|
cwd: frontendPath,
|
|
470
533
|
stdio: 'inherit',
|
|
471
|
-
shell: true
|
|
534
|
+
shell: true,
|
|
535
|
+
env: { ...process.env, VITE_API_URL: `http://localhost:${apiPort}`, PORT: frontendPort.toString() }
|
|
472
536
|
});
|
|
473
537
|
}, 5000);
|
|
474
538
|
}
|
package/package.json
CHANGED