@launchframe/cli 1.0.0 → 1.0.2
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/package.json +1 -1
- package/src/commands/cache.js +19 -14
- package/src/commands/database-console.js +64 -16
- package/src/commands/deploy-sync-features.js +14 -12
- package/src/commands/dev-logo.js +3 -2
- package/src/commands/docker-logs.js +26 -7
- package/src/commands/docker-up.js +17 -3
- package/src/commands/migration-create.js +1 -3
- package/src/commands/module.js +15 -13
- package/src/commands/service.js +22 -12
- package/src/commands/waitlist-logs.js +20 -3
- package/src/index.js +27 -19
package/package.json
CHANGED
package/src/commands/cache.js
CHANGED
|
@@ -3,24 +3,29 @@ const { clearCache, getCacheInfo } = require('../utils/service-cache');
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Clear service cache
|
|
6
|
+
* @param {Object} flags - Optional flags
|
|
7
|
+
* @param {boolean} flags.yes - Skip confirmation prompt
|
|
8
|
+
* @param {boolean} flags.y - Skip confirmation prompt (short form)
|
|
6
9
|
*/
|
|
7
|
-
async function cacheClear() {
|
|
10
|
+
async function cacheClear(flags = {}) {
|
|
8
11
|
console.log(chalk.yellow('\n⚠️ This will delete all cached services'));
|
|
9
12
|
console.log(chalk.gray('You will need to re-download on next init or service:add\n'));
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
|
|
14
|
+
if (!(flags.yes || flags.y)) {
|
|
15
|
+
const inquirer = require('inquirer');
|
|
16
|
+
const { confirmed } = await inquirer.prompt([{
|
|
17
|
+
type: 'confirm',
|
|
18
|
+
name: 'confirmed',
|
|
19
|
+
message: 'Continue with cache clear?',
|
|
20
|
+
default: false
|
|
21
|
+
}]);
|
|
22
|
+
|
|
23
|
+
if (!confirmed) {
|
|
24
|
+
console.log('Cancelled');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
22
27
|
}
|
|
23
|
-
|
|
28
|
+
|
|
24
29
|
await clearCache();
|
|
25
30
|
}
|
|
26
31
|
|
|
@@ -5,7 +5,7 @@ const inquirer = require('inquirer');
|
|
|
5
5
|
const { spawnSync } = require('child_process');
|
|
6
6
|
const { requireProject, getProjectConfig } = require('../utils/project-helpers');
|
|
7
7
|
|
|
8
|
-
async function databaseConsole({ remote = false } = {}) {
|
|
8
|
+
async function databaseConsole({ remote = false, query = null, skipPermission = false } = {}) {
|
|
9
9
|
requireProject();
|
|
10
10
|
|
|
11
11
|
const infrastructurePath = path.join(process.cwd(), 'infrastructure');
|
|
@@ -28,23 +28,45 @@ async function databaseConsole({ remote = false } = {}) {
|
|
|
28
28
|
|
|
29
29
|
const { vpsUser, vpsHost, vpsAppFolder } = config.deployment;
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
if (query) {
|
|
32
|
+
// Non-interactive query mode — pipe SQL via stdin, skip confirmation
|
|
33
|
+
const remoteCmd = `cd ${vpsAppFolder}/infrastructure && docker compose -f docker-compose.yml -f docker-compose.prod.yml exec -T database sh -c 'psql -U $POSTGRES_USER $POSTGRES_DB'`;
|
|
34
|
+
const result = spawnSync('ssh', ['-T', `${vpsUser}@${vpsHost}`, remoteCmd], {
|
|
35
|
+
input: query,
|
|
36
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
37
|
+
encoding: 'utf8'
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
41
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
42
|
+
|
|
43
|
+
if (result.status !== 0) {
|
|
44
|
+
console.error(chalk.red('\n❌ Could not execute query on the production database.'));
|
|
45
|
+
console.log(chalk.gray('Check that the VPS is reachable and services are running.\n'));
|
|
46
|
+
process.exit(1);
|
|
42
47
|
}
|
|
43
|
-
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
44
50
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
// 2. Warn before connecting to production (interactive mode only)
|
|
52
|
+
if (!skipPermission) {
|
|
53
|
+
console.log(chalk.yellow.bold('\n⚠️ You are about to connect to the PRODUCTION database.\n'));
|
|
54
|
+
console.log(chalk.gray(` Host: ${vpsHost}`));
|
|
55
|
+
console.log(chalk.gray(` Folder: ${vpsAppFolder}\n`));
|
|
56
|
+
|
|
57
|
+
const { confirmed } = await inquirer.prompt([
|
|
58
|
+
{
|
|
59
|
+
type: 'confirm',
|
|
60
|
+
name: 'confirmed',
|
|
61
|
+
message: 'Are you sure you want to open a console to the production database?',
|
|
62
|
+
default: false
|
|
63
|
+
}
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
if (!confirmed) {
|
|
67
|
+
console.log(chalk.gray('\nAborted.\n'));
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
48
70
|
}
|
|
49
71
|
|
|
50
72
|
console.log(chalk.blue.bold('\n🔌 Connecting to production database...\n'));
|
|
@@ -62,6 +84,32 @@ async function databaseConsole({ remote = false } = {}) {
|
|
|
62
84
|
process.exit(1);
|
|
63
85
|
}
|
|
64
86
|
} else {
|
|
87
|
+
if (query) {
|
|
88
|
+
// Non-interactive local query mode — pipe SQL via stdin
|
|
89
|
+
const psqlCmd = [
|
|
90
|
+
'compose', '-f', 'docker-compose.yml', '-f', 'docker-compose.dev.yml',
|
|
91
|
+
'exec', '-T', 'database', 'sh', '-c', 'psql -U $POSTGRES_USER $POSTGRES_DB'
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const result = spawnSync('docker', psqlCmd, {
|
|
95
|
+
cwd: infrastructurePath,
|
|
96
|
+
input: query,
|
|
97
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
98
|
+
encoding: 'utf8'
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
102
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
103
|
+
|
|
104
|
+
if (result.status !== 0) {
|
|
105
|
+
console.error(chalk.red('\n❌ Could not execute query on the local database container.'));
|
|
106
|
+
console.log(chalk.gray('Make sure services are running:'));
|
|
107
|
+
console.log(chalk.white(' launchframe docker:up\n'));
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
65
113
|
console.log(chalk.blue.bold('\n🗄️ Opening local database console...\n'));
|
|
66
114
|
|
|
67
115
|
// Let the shell inside the container expand $POSTGRES_USER / $POSTGRES_DB
|
|
@@ -34,7 +34,7 @@ function sqlBool(val) {
|
|
|
34
34
|
return val ? 'true' : 'false';
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
async function deploySyncFeatures() {
|
|
37
|
+
async function deploySyncFeatures(flags = {}) {
|
|
38
38
|
requireProject();
|
|
39
39
|
|
|
40
40
|
// Step 1 — Project + infrastructure check
|
|
@@ -162,18 +162,20 @@ async function deploySyncFeatures() {
|
|
|
162
162
|
console.log(chalk.gray(` Remote host: ${vpsHost}\n`));
|
|
163
163
|
console.log(chalk.red('This will TRUNCATE subscription_plan_features (cascades to feature values).\n'));
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
{
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
165
|
+
if (!flags.yes) {
|
|
166
|
+
const { confirmed } = await inquirer.prompt([
|
|
167
|
+
{
|
|
168
|
+
type: 'confirm',
|
|
169
|
+
name: 'confirmed',
|
|
170
|
+
message: 'Are you sure you want to sync features to production?',
|
|
171
|
+
default: false
|
|
172
|
+
}
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
if (!confirmed) {
|
|
176
|
+
console.log(chalk.gray('\nAborted.\n'));
|
|
177
|
+
process.exit(0);
|
|
171
178
|
}
|
|
172
|
-
]);
|
|
173
|
-
|
|
174
|
-
if (!confirmed) {
|
|
175
|
-
console.log(chalk.gray('\nAborted.\n'));
|
|
176
|
-
process.exit(0);
|
|
177
179
|
}
|
|
178
180
|
|
|
179
181
|
// Step 9 — Build sync SQL transaction
|
package/src/commands/dev-logo.js
CHANGED
|
@@ -30,12 +30,13 @@ function writeFile(filePath, data) {
|
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Generate and inject logo/favicon assets across all relevant frontend services
|
|
33
|
+
* @param {string} svgPath - Optional path to SVG file (defaults to <projectRoot>/logo.svg)
|
|
33
34
|
*/
|
|
34
|
-
async function devLogo() {
|
|
35
|
+
async function devLogo(svgPath) {
|
|
35
36
|
requireProject();
|
|
36
37
|
|
|
37
38
|
const cwd = process.cwd();
|
|
38
|
-
const logoPath = path.join(cwd, 'logo.svg');
|
|
39
|
+
const logoPath = svgPath || path.join(cwd, 'logo.svg');
|
|
39
40
|
|
|
40
41
|
if (!fs.existsSync(logoPath)) {
|
|
41
42
|
console.error(chalk.red('\n❌ Error: logo.svg not found'));
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
|
-
const { spawn } = require('child_process');
|
|
4
|
+
const { spawn, execSync } = require('child_process');
|
|
5
5
|
const { requireProject } = require('../utils/project-helpers');
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* View logs from Docker services
|
|
9
|
+
* @param {string} service - Optional service name to filter logs
|
|
10
|
+
* @param {Object} flags - Optional flags
|
|
11
|
+
* @param {boolean} flags['no-follow'] - Snapshot mode: print lines and exit (non-interactive)
|
|
12
|
+
* @param {number} flags.tail - Number of lines to show (default 100, only used with --no-follow)
|
|
9
13
|
*/
|
|
10
|
-
async function dockerLogs() {
|
|
14
|
+
async function dockerLogs(service, flags = {}) {
|
|
11
15
|
requireProject();
|
|
12
16
|
|
|
13
17
|
const infrastructurePath = path.join(process.cwd(), 'infrastructure');
|
|
@@ -18,9 +22,24 @@ async function dockerLogs() {
|
|
|
18
22
|
process.exit(1);
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
const service = process.argv[3];
|
|
25
|
+
const noFollow = flags['no-follow'];
|
|
23
26
|
|
|
27
|
+
if (noFollow) {
|
|
28
|
+
// Snapshot mode — print tail and exit (non-interactive, suitable for MCP)
|
|
29
|
+
const tail = flags.tail || 100;
|
|
30
|
+
const args = ['-f', 'docker-compose.yml', '-f', 'docker-compose.dev.yml', 'logs', '--no-follow', '--tail', String(tail)];
|
|
31
|
+
if (service) args.push(service);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
execSync(`docker-compose ${args.join(' ')}`, { cwd: infrastructurePath, stdio: 'inherit' });
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error(chalk.red('\n❌ Error viewing logs:'), error.message);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Streaming mode (interactive)
|
|
24
43
|
console.log(chalk.blue.bold('\n📋 Docker Service Logs\n'));
|
|
25
44
|
|
|
26
45
|
if (service) {
|
|
@@ -34,14 +53,14 @@ async function dockerLogs() {
|
|
|
34
53
|
|
|
35
54
|
try {
|
|
36
55
|
const logsCommand = 'docker-compose';
|
|
37
|
-
const
|
|
56
|
+
const spawnArgs = ['-f', 'docker-compose.yml', '-f', 'docker-compose.dev.yml', 'logs', '-f'];
|
|
38
57
|
|
|
39
58
|
if (service) {
|
|
40
|
-
|
|
59
|
+
spawnArgs.push(service);
|
|
41
60
|
}
|
|
42
61
|
|
|
43
62
|
// Use spawn to stream output in real-time
|
|
44
|
-
const child = spawn(logsCommand,
|
|
63
|
+
const child = spawn(logsCommand, spawnArgs, {
|
|
45
64
|
cwd: infrastructurePath,
|
|
46
65
|
stdio: 'inherit',
|
|
47
66
|
shell: true
|
|
@@ -7,8 +7,10 @@ const { requireProject, getProjectConfig } = require('../utils/project-helpers')
|
|
|
7
7
|
/**
|
|
8
8
|
* Start Docker services (all or specific service)
|
|
9
9
|
* @param {string} serviceName - Optional service name to start (e.g., 'docs', 'backend')
|
|
10
|
+
* @param {Object} flags - Optional flags
|
|
11
|
+
* @param {boolean} flags.detach - Run detached (docker-compose up -d) instead of watch mode
|
|
10
12
|
*/
|
|
11
|
-
async function dockerUp(serviceName) {
|
|
13
|
+
async function dockerUp(serviceName, flags = {}) {
|
|
12
14
|
requireProject();
|
|
13
15
|
|
|
14
16
|
const infrastructurePath = path.join(process.cwd(), 'infrastructure');
|
|
@@ -19,14 +21,26 @@ async function dockerUp(serviceName) {
|
|
|
19
21
|
process.exit(1);
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
if (flags.detach) {
|
|
25
|
+
// Detached mode — start services in background (no watch, no blocking)
|
|
26
|
+
const upCommand = serviceName
|
|
27
|
+
? `docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d ${serviceName}`
|
|
28
|
+
: 'docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d';
|
|
29
|
+
|
|
30
|
+
console.log(chalk.gray(`Running: ${upCommand}\n`));
|
|
31
|
+
execSync(upCommand, { cwd: infrastructurePath, stdio: 'inherit' });
|
|
32
|
+
console.log(chalk.green.bold('\n✅ Services started in detached mode.\n'));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
22
36
|
// Check Docker Compose version for watch support
|
|
23
37
|
try {
|
|
24
38
|
const composeVersion = execSync('docker compose version', { encoding: 'utf8' });
|
|
25
39
|
const versionMatch = composeVersion.match(/v?(\d+)\.(\d+)\.(\d+)/);
|
|
26
|
-
|
|
40
|
+
|
|
27
41
|
if (versionMatch) {
|
|
28
42
|
const [, major, minor] = versionMatch.map(Number);
|
|
29
|
-
|
|
43
|
+
|
|
30
44
|
if (major < 2 || (major === 2 && minor < 22)) {
|
|
31
45
|
console.error(chalk.red('\n❌ Error: Docker Compose v2.22+ is required for watch support'));
|
|
32
46
|
console.log(chalk.yellow(`Current version: Docker Compose v${major}.${minor}`));
|
|
@@ -4,7 +4,7 @@ const chalk = require('chalk');
|
|
|
4
4
|
const { execSync } = require('child_process');
|
|
5
5
|
const { requireProject } = require('../utils/project-helpers');
|
|
6
6
|
|
|
7
|
-
async function migrateCreate() {
|
|
7
|
+
async function migrateCreate(migrationName) {
|
|
8
8
|
requireProject();
|
|
9
9
|
|
|
10
10
|
const infrastructurePath = path.join(process.cwd(), 'infrastructure');
|
|
@@ -15,8 +15,6 @@ async function migrateCreate() {
|
|
|
15
15
|
process.exit(1);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const migrationName = process.argv[3];
|
|
19
|
-
|
|
20
18
|
if (!migrationName) {
|
|
21
19
|
console.error(chalk.red('\n❌ Error: migration name is required'));
|
|
22
20
|
console.log(chalk.gray('Usage: launchframe migrate:create <name>\n'));
|
package/src/commands/module.js
CHANGED
|
@@ -41,7 +41,7 @@ async function moduleList() {
|
|
|
41
41
|
console.log(' launchframe module:add <module-name>');
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
async function moduleAdd(moduleName) {
|
|
44
|
+
async function moduleAdd(moduleName, flags = {}) {
|
|
45
45
|
requireProject();
|
|
46
46
|
|
|
47
47
|
// Validate module exists in registry
|
|
@@ -97,16 +97,18 @@ async function moduleAdd(moduleName) {
|
|
|
97
97
|
console.log(mod.description);
|
|
98
98
|
console.log(`Affects services: ${mod.services.join(', ')}`);
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
if (!(flags.yes || flags.y)) {
|
|
101
|
+
const { confirmed } = await inquirer.prompt([{
|
|
102
|
+
type: 'confirm',
|
|
103
|
+
name: 'confirmed',
|
|
104
|
+
message: `Add module "${mod.displayName}" to your project?`,
|
|
105
|
+
default: true
|
|
106
|
+
}]);
|
|
107
|
+
|
|
108
|
+
if (!confirmed) {
|
|
109
|
+
console.log('Installation cancelled');
|
|
110
|
+
process.exit(0);
|
|
111
|
+
}
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
const affectedServices = [...new Set(Object.keys(MODULE_CONFIG[moduleName] || {}))].filter(s => s !== 'infrastructure');
|
|
@@ -139,8 +141,8 @@ async function moduleAdd(moduleName) {
|
|
|
139
141
|
await dockerBuild(service);
|
|
140
142
|
}
|
|
141
143
|
|
|
142
|
-
// Restart the full stack
|
|
143
|
-
await dockerUp();
|
|
144
|
+
// Restart the full stack (detached when running non-interactively, watch otherwise)
|
|
145
|
+
await dockerUp(undefined, flags.yes || flags.y ? { detach: true } : {});
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
module.exports = { moduleAdd, moduleList };
|
package/src/commands/service.js
CHANGED
|
@@ -10,7 +10,7 @@ const { updateEnvFile } = require('../utils/env-generator');
|
|
|
10
10
|
const { checkGitHubAccess, showAccessDeniedMessage } = require('../utils/github-access');
|
|
11
11
|
const { ensureCacheReady, getServicePath } = require('../utils/service-cache');
|
|
12
12
|
|
|
13
|
-
async function serviceAdd(serviceName) {
|
|
13
|
+
async function serviceAdd(serviceName, flags = {}) {
|
|
14
14
|
// STEP 1: Validation
|
|
15
15
|
console.log(chalk.blue(`Installing ${serviceName} service...`));
|
|
16
16
|
|
|
@@ -49,16 +49,18 @@ async function serviceAdd(serviceName) {
|
|
|
49
49
|
console.log(`Tech stack: ${service.techStack}`);
|
|
50
50
|
console.log(`Dependencies: ${service.dependencies.join(', ')}`);
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
if (!(flags.yes || flags.y)) {
|
|
53
|
+
const { confirmed } = await inquirer.prompt([{
|
|
54
|
+
type: 'confirm',
|
|
55
|
+
name: 'confirmed',
|
|
56
|
+
message: 'Continue with installation?',
|
|
57
|
+
default: true
|
|
58
|
+
}]);
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
if (!confirmed) {
|
|
61
|
+
console.log('Installation cancelled');
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
// STEP 3: Get service files (from cache in production, local in dev)
|
|
@@ -124,7 +126,7 @@ async function serviceAdd(serviceName) {
|
|
|
124
126
|
|
|
125
127
|
// STEP 4: Service-specific prompts (e.g., Airtable credentials)
|
|
126
128
|
console.log(chalk.blue('\nConfiguring service...'));
|
|
127
|
-
const envValues = await runServicePrompts(service);
|
|
129
|
+
const envValues = await runServicePrompts(service, flags);
|
|
128
130
|
|
|
129
131
|
// STEP 5: Replace template variables
|
|
130
132
|
console.log(chalk.blue('\nCustomizing service for your project...'));
|
|
@@ -265,9 +267,17 @@ async function serviceAdd(serviceName) {
|
|
|
265
267
|
console.log(`\n📖 See README.md in ${serviceName}/ for more details.`);
|
|
266
268
|
}
|
|
267
269
|
|
|
268
|
-
async function runServicePrompts(service) {
|
|
270
|
+
async function runServicePrompts(service, flags = {}) {
|
|
269
271
|
const envValues = {};
|
|
270
272
|
|
|
273
|
+
if (flags.yes || flags.y) {
|
|
274
|
+
// Non-interactive mode — use empty values; configure manually after install
|
|
275
|
+
for (const key of Object.keys(service.envVars)) {
|
|
276
|
+
envValues[key] = '';
|
|
277
|
+
}
|
|
278
|
+
return envValues;
|
|
279
|
+
}
|
|
280
|
+
|
|
271
281
|
// Prompt for each required env var
|
|
272
282
|
for (const [key, description] of Object.entries(service.envVars)) {
|
|
273
283
|
const { value } = await inquirer.prompt([{
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
|
-
const { spawn } = require('child_process');
|
|
2
|
+
const { spawn, spawnSync } = require('child_process');
|
|
3
3
|
const { requireProject, getProjectConfig, isWaitlistInstalled } = require('../utils/project-helpers');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* View waitlist logs from VPS
|
|
6
|
+
* View waitlist logs from VPS
|
|
7
|
+
* @param {Object} flags - Optional flags
|
|
8
|
+
* @param {boolean} flags['no-follow'] - Snapshot mode: print tail and exit (non-interactive)
|
|
9
|
+
* @param {number} flags.tail - Number of lines to show (default 100, used with --no-follow)
|
|
7
10
|
*/
|
|
8
|
-
async function waitlistLogs() {
|
|
11
|
+
async function waitlistLogs(flags = {}) {
|
|
9
12
|
requireProject();
|
|
10
13
|
|
|
11
14
|
console.log(chalk.blue.bold('\n📋 Waitlist Logs\n'));
|
|
@@ -28,6 +31,20 @@ async function waitlistLogs() {
|
|
|
28
31
|
|
|
29
32
|
const { vpsHost, vpsUser, vpsAppFolder } = config.deployment;
|
|
30
33
|
|
|
34
|
+
if (flags['no-follow']) {
|
|
35
|
+
// Snapshot mode — print tail and exit (non-interactive, suitable for MCP)
|
|
36
|
+
const tail = flags.tail || 100;
|
|
37
|
+
const sshCmd = `cd ${vpsAppFolder}/waitlist && docker-compose -f docker-compose.waitlist.yml logs --no-follow --tail=${tail}`;
|
|
38
|
+
|
|
39
|
+
const result = spawnSync('ssh', [`${vpsUser}@${vpsHost}`, sshCmd], { stdio: 'inherit' });
|
|
40
|
+
|
|
41
|
+
if (result.status !== 0 && result.status !== null) {
|
|
42
|
+
console.log(chalk.yellow(`\n⚠️ Process exited with code ${result.status}\n`));
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Streaming mode (interactive)
|
|
31
48
|
console.log(chalk.gray('Connecting to VPS and streaming logs...\n'));
|
|
32
49
|
console.log(chalk.gray('Press Ctrl+C to exit\n'));
|
|
33
50
|
|
package/src/index.js
CHANGED
|
@@ -63,14 +63,22 @@ function parseFlags(args) {
|
|
|
63
63
|
for (let i = 1; i < args.length; i++) {
|
|
64
64
|
const arg = args[i];
|
|
65
65
|
if (arg.startsWith('--')) {
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
const withoutDashes = arg.substring(2);
|
|
67
|
+
const eqIndex = withoutDashes.indexOf('=');
|
|
68
|
+
if (eqIndex !== -1) {
|
|
69
|
+
// --key=value format
|
|
70
|
+
const flagName = withoutDashes.substring(0, eqIndex);
|
|
71
|
+
flags[flagName] = withoutDashes.substring(eqIndex + 1) || true;
|
|
72
72
|
} else {
|
|
73
|
-
|
|
73
|
+
const flagName = withoutDashes;
|
|
74
|
+
const nextArg = args[i + 1];
|
|
75
|
+
// Check if next arg is a value (not a flag)
|
|
76
|
+
if (nextArg && !nextArg.startsWith('-')) {
|
|
77
|
+
flags[flagName] = nextArg;
|
|
78
|
+
i++; // Skip next arg since we consumed it
|
|
79
|
+
} else {
|
|
80
|
+
flags[flagName] = true; // Boolean flag
|
|
81
|
+
}
|
|
74
82
|
}
|
|
75
83
|
} else if (arg.startsWith('-') && arg.length === 2) {
|
|
76
84
|
const flagName = arg.substring(1);
|
|
@@ -132,7 +140,7 @@ async function main() {
|
|
|
132
140
|
await deployBuild(args[1]); // Optional service name
|
|
133
141
|
break;
|
|
134
142
|
case 'deploy:sync-features':
|
|
135
|
-
await deploySyncFeatures();
|
|
143
|
+
await deploySyncFeatures(flags);
|
|
136
144
|
break;
|
|
137
145
|
case 'waitlist:deploy':
|
|
138
146
|
await waitlistDeploy();
|
|
@@ -144,19 +152,19 @@ async function main() {
|
|
|
144
152
|
await waitlistDown();
|
|
145
153
|
break;
|
|
146
154
|
case 'waitlist:logs':
|
|
147
|
-
await waitlistLogs();
|
|
155
|
+
await waitlistLogs(flags);
|
|
148
156
|
break;
|
|
149
157
|
case 'docker:build':
|
|
150
158
|
await dockerBuild(args[1]); // Optional service name
|
|
151
159
|
break;
|
|
152
160
|
case 'docker:up':
|
|
153
|
-
await dockerUp(args[1]
|
|
161
|
+
await dockerUp(args[1] || flags.service, flags);
|
|
154
162
|
break;
|
|
155
163
|
case 'docker:down':
|
|
156
164
|
await dockerDown();
|
|
157
165
|
break;
|
|
158
166
|
case 'docker:logs':
|
|
159
|
-
await dockerLogs();
|
|
167
|
+
await dockerLogs(args[1] || flags.service, flags);
|
|
160
168
|
break;
|
|
161
169
|
case 'docker:destroy':
|
|
162
170
|
await dockerDestroy({ force: flags.force || flags.f });
|
|
@@ -165,24 +173,24 @@ async function main() {
|
|
|
165
173
|
await migrateRun();
|
|
166
174
|
break;
|
|
167
175
|
case 'migration:create':
|
|
168
|
-
await migrateCreate();
|
|
176
|
+
await migrateCreate(args[1] || flags.name);
|
|
169
177
|
break;
|
|
170
178
|
case 'migration:revert':
|
|
171
179
|
await migrateRevert();
|
|
172
180
|
break;
|
|
173
181
|
case 'database:console':
|
|
174
|
-
await databaseConsole({ remote: flags.remote });
|
|
182
|
+
await databaseConsole({ remote: flags.remote, query: flags.query, skipPermission: !!flags['skip-permission'] });
|
|
175
183
|
break;
|
|
176
184
|
case 'doctor':
|
|
177
185
|
await doctor();
|
|
178
186
|
break;
|
|
179
187
|
case 'service:add':
|
|
180
|
-
if (!args[1]) {
|
|
188
|
+
if (!args[1] && !flags.service) {
|
|
181
189
|
console.error(chalk.red('Error: Service name required'));
|
|
182
190
|
console.log('Usage: launchframe service:add <service-name>');
|
|
183
191
|
process.exit(1);
|
|
184
192
|
}
|
|
185
|
-
await serviceAdd(args[1]);
|
|
193
|
+
await serviceAdd(args[1] || flags.service, flags);
|
|
186
194
|
break;
|
|
187
195
|
case 'service:list':
|
|
188
196
|
await serviceList();
|
|
@@ -196,18 +204,18 @@ async function main() {
|
|
|
196
204
|
await serviceRemove(args[1]);
|
|
197
205
|
break;
|
|
198
206
|
case 'module:add':
|
|
199
|
-
if (!args[1]) {
|
|
207
|
+
if (!args[1] && !flags.name) {
|
|
200
208
|
console.error(chalk.red('Error: Module name required'));
|
|
201
209
|
console.log('Usage: launchframe module:add <module-name>');
|
|
202
210
|
process.exit(1);
|
|
203
211
|
}
|
|
204
|
-
await moduleAdd(args[1]);
|
|
212
|
+
await moduleAdd(args[1] || flags.name, flags);
|
|
205
213
|
break;
|
|
206
214
|
case 'module:list':
|
|
207
215
|
await moduleList();
|
|
208
216
|
break;
|
|
209
217
|
case 'cache:clear':
|
|
210
|
-
await cacheClear();
|
|
218
|
+
await cacheClear(flags);
|
|
211
219
|
break;
|
|
212
220
|
case 'cache:info':
|
|
213
221
|
await cacheInfo();
|
|
@@ -222,7 +230,7 @@ async function main() {
|
|
|
222
230
|
await devQueue();
|
|
223
231
|
break;
|
|
224
232
|
case 'dev:logo':
|
|
225
|
-
await devLogo();
|
|
233
|
+
await devLogo(args[1] || flags.svg);
|
|
226
234
|
break;
|
|
227
235
|
case 'dev:npm-install':
|
|
228
236
|
if (!args[1]) {
|