@launchframe/cli 1.0.0-beta.3 → 1.0.0-beta.30

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.
@@ -0,0 +1,165 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const crypto = require('crypto');
4
+ const chalk = require('chalk');
5
+ const bcrypt = require('bcryptjs');
6
+ const { spawnSync } = require('child_process');
7
+ const { requireProject, getProjectConfig } = require('../utils/project-helpers');
8
+
9
+ const FIRST_NAMES = [
10
+ 'Alice', 'Bob', 'Carol', 'David', 'Eve', 'Frank', 'Grace', 'Henry',
11
+ 'Iris', 'Jack', 'Karen', 'Leo', 'Mia', 'Nathan', 'Olivia'
12
+ ];
13
+
14
+ const LAST_NAMES = [
15
+ 'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller',
16
+ 'Davis', 'Wilson', 'Moore', 'Taylor', 'Anderson', 'Thomas', 'Jackson', 'White'
17
+ ];
18
+
19
+ function randomElement(arr) {
20
+ return arr[Math.floor(Math.random() * arr.length)];
21
+ }
22
+
23
+ function generateDummyUser() {
24
+ const firstName = randomElement(FIRST_NAMES);
25
+ const lastName = randomElement(LAST_NAMES);
26
+ const suffix = String(Math.floor(1000 + Math.random() * 9000));
27
+ const email = `${firstName.toLowerCase()}.${lastName.toLowerCase()}${suffix}@gmail.com`;
28
+ return { firstName, lastName, name: `${firstName} ${lastName}`, email, suffix };
29
+ }
30
+
31
+ function checkEmailExists(infrastructurePath, email) {
32
+ const composeArgs = [
33
+ 'compose', '-f', 'docker-compose.yml', '-f', 'docker-compose.dev.yml',
34
+ 'exec', '-T', 'database', 'sh', '-c', 'psql -U $POSTGRES_USER $POSTGRES_DB -t -c "SELECT COUNT(*) FROM users WHERE email = \'__EMAIL__\'"'
35
+ .replace('__EMAIL__', email.replace(/'/g, "''"))
36
+ ];
37
+
38
+ const result = spawnSync('docker', composeArgs, { cwd: infrastructurePath, encoding: 'utf8' });
39
+ if (result.status !== 0) return false;
40
+ const count = parseInt((result.stdout || '').trim(), 10);
41
+ return count > 0;
42
+ }
43
+
44
+ async function devAddUser() {
45
+ requireProject();
46
+
47
+ const infrastructurePath = path.join(process.cwd(), 'infrastructure');
48
+
49
+ if (!fs.existsSync(infrastructurePath)) {
50
+ console.error(chalk.red('\n❌ Error: infrastructure/ directory not found'));
51
+ console.log(chalk.gray('Make sure you are in the root of your LaunchFrame project.\n'));
52
+ process.exit(1);
53
+ }
54
+
55
+ // Check database container is running
56
+ const psResult = spawnSync(
57
+ 'docker',
58
+ [
59
+ 'compose', '-f', 'docker-compose.yml', '-f', 'docker-compose.dev.yml',
60
+ 'ps', '--status', 'running', '-q', 'database'
61
+ ],
62
+ { cwd: infrastructurePath, encoding: 'utf8' }
63
+ );
64
+
65
+ if (!psResult.stdout || psResult.stdout.trim() === '') {
66
+ console.error(chalk.red('\n❌ Database container is not running.'));
67
+ console.log(chalk.gray('Start local services first:'));
68
+ console.log(chalk.white(' launchframe docker:up\n'));
69
+ process.exit(1);
70
+ }
71
+
72
+ // Generate user with unique email (up to 5 attempts)
73
+ let user = null;
74
+ for (let attempt = 0; attempt < 5; attempt++) {
75
+ const candidate = generateDummyUser();
76
+ if (!checkEmailExists(infrastructurePath, candidate.email)) {
77
+ user = candidate;
78
+ break;
79
+ }
80
+ }
81
+
82
+ if (!user) {
83
+ console.error(chalk.red('\n❌ Could not generate a unique email after 5 attempts.'));
84
+ process.exit(1);
85
+ }
86
+
87
+ const config = getProjectConfig();
88
+ const isMultiTenant = config.variants?.tenancy === 'multi-tenant';
89
+
90
+ const passwordHash = await bcrypt.hash('test123', 10);
91
+ const accountId = crypto.randomUUID();
92
+
93
+ const projectTitle = `Demo Project`;
94
+ const projectSlug = `demo-project-${user.suffix}`;
95
+
96
+ const projectInsert = isMultiTenant ? `
97
+ INSERT INTO projects (user_id, title, slug, description, created_at, updated_at)
98
+ VALUES (
99
+ new_user_id,
100
+ '${projectTitle}',
101
+ '${projectSlug}',
102
+ 'Auto-generated demo project',
103
+ NOW(),
104
+ NOW()
105
+ );` : '';
106
+
107
+ const sqlScript = `
108
+ DO $$
109
+ DECLARE
110
+ new_user_id INT;
111
+ BEGIN
112
+ INSERT INTO users (email, name, role, email_verified, is_active, created_at, updated_at)
113
+ VALUES (
114
+ '${user.email.replace(/'/g, "''")}',
115
+ '${user.name.replace(/'/g, "''")}',
116
+ 'business_user',
117
+ true,
118
+ true,
119
+ NOW(),
120
+ NOW()
121
+ )
122
+ RETURNING id INTO new_user_id;
123
+
124
+ INSERT INTO accounts (id, user_id, account_id, provider_id, password, created_at, updated_at)
125
+ VALUES (
126
+ '${accountId}',
127
+ new_user_id,
128
+ new_user_id::text,
129
+ 'credential',
130
+ '${passwordHash.replace(/'/g, "''")}',
131
+ NOW(),
132
+ NOW()
133
+ );
134
+ ${projectInsert}
135
+ END $$;
136
+ `;
137
+
138
+ const execResult = spawnSync(
139
+ 'docker',
140
+ [
141
+ 'compose', '-f', 'docker-compose.yml', '-f', 'docker-compose.dev.yml',
142
+ 'exec', '-T', 'database', 'sh', '-c', 'psql -v ON_ERROR_STOP=1 -U $POSTGRES_USER $POSTGRES_DB'
143
+ ],
144
+ { cwd: infrastructurePath, input: sqlScript, encoding: 'utf8' }
145
+ );
146
+
147
+ if (execResult.status !== 0) {
148
+ console.error(chalk.red('\n❌ Failed to insert user into database.'));
149
+ if (execResult.stderr) {
150
+ console.error(chalk.gray(execResult.stderr));
151
+ }
152
+ process.exit(1);
153
+ }
154
+
155
+ console.log(chalk.green('\n✅ User created!'));
156
+ console.log(chalk.gray(` Name: ${user.name}`));
157
+ console.log(chalk.gray(` Email: ${user.email}`));
158
+ console.log(chalk.gray(` Password: test123`));
159
+ if (isMultiTenant) {
160
+ console.log(chalk.gray(` Project: ${projectTitle} (${projectSlug})`));
161
+ }
162
+ console.log();
163
+ }
164
+
165
+ module.exports = { devAddUser };
@@ -0,0 +1,85 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const { spawnSync, spawn } = require('child_process');
5
+ const { requireProject } = require('../utils/project-helpers');
6
+
7
+ async function devQueue() {
8
+ requireProject();
9
+
10
+ const infrastructurePath = path.join(process.cwd(), 'infrastructure');
11
+
12
+ if (!fs.existsSync(infrastructurePath)) {
13
+ console.error(chalk.red('\n❌ Error: infrastructure/ directory not found'));
14
+ console.log(chalk.gray('Make sure you are in the root of your LaunchFrame project.\n'));
15
+ process.exit(1);
16
+ }
17
+
18
+ // Check backend container is running
19
+ const psResult = spawnSync(
20
+ 'docker',
21
+ [
22
+ 'compose', '-f', 'docker-compose.yml', '-f', 'docker-compose.dev.yml',
23
+ 'ps', '--status', 'running', '-q', 'backend'
24
+ ],
25
+ { cwd: infrastructurePath, encoding: 'utf8' }
26
+ );
27
+
28
+ if (!psResult.stdout || psResult.stdout.trim() === '') {
29
+ console.error(chalk.red('\n❌ Backend container is not running.'));
30
+ console.log(chalk.gray('Start local services first:'));
31
+ console.log(chalk.white(' launchframe docker:up\n'));
32
+ process.exit(1);
33
+ }
34
+
35
+ // Read BULL_ADMIN_TOKEN from backend container
36
+ const tokenResult = spawnSync(
37
+ 'docker',
38
+ [
39
+ 'compose', '-f', 'docker-compose.yml', '-f', 'docker-compose.dev.yml',
40
+ 'exec', '-T', 'backend', 'sh', '-c', 'echo $BULL_ADMIN_TOKEN'
41
+ ],
42
+ { cwd: infrastructurePath, encoding: 'utf8' }
43
+ );
44
+
45
+ const token = (tokenResult.stdout || '').trim();
46
+
47
+ if (!token) {
48
+ console.error(chalk.red('\n❌ Could not read BULL_ADMIN_TOKEN from backend container.'));
49
+ console.log(chalk.gray('Make sure the backend is fully started and BULL_ADMIN_TOKEN is set in your .env\n'));
50
+ process.exit(1);
51
+ }
52
+
53
+ // Read BACKEND_PORT from backend container (fallback to 4000)
54
+ const portResult = spawnSync(
55
+ 'docker',
56
+ [
57
+ 'compose', '-f', 'docker-compose.yml', '-f', 'docker-compose.dev.yml',
58
+ 'exec', '-T', 'backend', 'sh', '-c', 'echo $BACKEND_PORT'
59
+ ],
60
+ { cwd: infrastructurePath, encoding: 'utf8' }
61
+ );
62
+
63
+ const port = (portResult.stdout || '').trim() || '4000';
64
+
65
+ const url = `http://localhost:${port}/admin/queues/${token}`;
66
+
67
+ console.log(chalk.green('\n Opening Bull queue dashboard...'));
68
+ console.log(chalk.gray(` ${url}\n`));
69
+
70
+ // Open URL with platform-appropriate command
71
+ const platform = process.platform;
72
+ let openCmd;
73
+ if (platform === 'darwin') {
74
+ openCmd = 'open';
75
+ } else if (platform === 'win32') {
76
+ openCmd = 'start';
77
+ } else {
78
+ openCmd = 'xdg-open';
79
+ }
80
+
81
+ const child = spawn(openCmd, [url], { detached: true, stdio: 'ignore' });
82
+ child.unref();
83
+ }
84
+
85
+ module.exports = { devQueue };
@@ -7,16 +7,20 @@ const { isLaunchFrameProject, isWaitlistInstalled } = require('../utils/project-
7
7
  function help() {
8
8
  const inProject = isLaunchFrameProject();
9
9
 
10
- console.log(chalk.blue.bold('\n🚀 LaunchFrame CLI\n'));
10
+ console.log(chalk.blue.bold('\nLaunchFrame CLI\n'));
11
11
  console.log(chalk.white('Usage:'));
12
- console.log(chalk.gray(' launchframe [command]\n'));
12
+ console.log(chalk.gray(' launchframe [command] [options]\n'));
13
+ console.log(chalk.white('Global options:'));
14
+ console.log(chalk.gray(' --verbose, -v Show detailed output'));
15
+ console.log(chalk.gray(' --version Show version number\n'));
13
16
 
14
17
  if (inProject) {
15
18
  console.log(chalk.white('Deployment commands:'));
16
19
  console.log(chalk.gray(' deploy:configure Configure production deployment settings'));
17
20
  console.log(chalk.gray(' deploy:set-env Configure production environment variables'));
18
21
  console.log(chalk.gray(' deploy:init Initialize VPS and build Docker images'));
19
- console.log(chalk.gray(' deploy:up Start services on VPS\n'));
22
+ console.log(chalk.gray(' deploy:up Start services on VPS'));
23
+ console.log(chalk.gray(' deploy:build [service] Build, push, and deploy (all or specific service)\n'));
20
24
 
21
25
  // Conditionally show waitlist commands
22
26
  if (isWaitlistInstalled()) {
@@ -33,6 +37,14 @@ function help() {
33
37
  console.log(chalk.gray(' docker:logs [service] View logs from all services or specific service'));
34
38
  console.log(chalk.gray(' docker:destroy Remove all resources (containers, volumes, images)'));
35
39
  console.log(chalk.gray(' --force, -f Skip confirmation prompt\n'));
40
+ console.log(chalk.white('Database Migrations:'));
41
+ console.log(chalk.gray(' migration:run Run pending database migrations'));
42
+ console.log(chalk.gray(' migration:create Create new database migration'));
43
+ console.log(chalk.gray(' migration:revert Revert last database migration\n'));
44
+ console.log(chalk.white('Database:'));
45
+ console.log(chalk.gray(' database:console Open a PostgreSQL console (local)'));
46
+ console.log(chalk.gray(' --remote Connect to the production database\n'));
47
+
36
48
  console.log(chalk.white('Service Management:'));
37
49
  console.log(chalk.gray(' service:add <name> Add an optional service to your project'));
38
50
  console.log(chalk.gray(' service:list List available services'));
@@ -40,11 +52,17 @@ function help() {
40
52
  console.log(chalk.white('Available Services:'));
41
53
  console.log(chalk.gray(' waitlist Coming soon page with email collection\n'));
42
54
  console.log(chalk.white('Cache Management:'));
43
- console.log(chalk.gray(' cache:info Show cache location, size, and cached modules'));
55
+ console.log(chalk.gray(' cache:info Show cache location, size, and cached services'));
44
56
  console.log(chalk.gray(' cache:update Force update cache to latest version'));
45
57
  console.log(chalk.gray(' cache:clear Delete cache (re-download on next use)\n'));
58
+ console.log(chalk.white('Dev Helpers:'));
59
+ console.log(chalk.gray(' dev:add-user Generate and insert a random test user into the local database'));
60
+ console.log(chalk.gray(' dev:queue Open the Bull queue dashboard in the browser\n'));
46
61
  console.log(chalk.white('Other commands:'));
47
62
  console.log(chalk.gray(' doctor Check project health and configuration'));
63
+ console.log(chalk.gray(' telemetry Show telemetry status'));
64
+ console.log(chalk.gray(' --enable Enable anonymous usage data collection'));
65
+ console.log(chalk.gray(' --disable Disable anonymous usage data collection'));
48
66
  console.log(chalk.gray(' help Show this help message\n'));
49
67
  console.log(chalk.white('Examples:'));
50
68
  console.log(chalk.gray(' # Deploy full app to production'));
@@ -70,19 +88,19 @@ function help() {
70
88
  } else {
71
89
  console.log(chalk.white('Available commands:'));
72
90
  console.log(chalk.gray(' init Initialize a new LaunchFrame project'));
73
- console.log(chalk.gray(' --project-name <name> Project name (skips prompt)'));
74
- console.log(chalk.gray(' --tenancy <single|multi> Tenancy model (skips prompt)'));
75
- console.log(chalk.gray(' --user-model <b2b|b2b2c> User model (skips prompt)'));
76
91
  console.log(chalk.gray(' help Show this help message\n'));
92
+ console.log(chalk.white('Telemetry:'));
93
+ console.log(chalk.gray(' telemetry Show telemetry status'));
94
+ console.log(chalk.gray(' --enable Enable anonymous usage data collection'));
95
+ console.log(chalk.gray(' --disable Disable anonymous usage data collection\n'));
77
96
  console.log(chalk.white('Cache Management:'));
78
- console.log(chalk.gray(' cache:info Show cache location, size, and cached modules'));
97
+ console.log(chalk.gray(' cache:info Show cache location, size, and cached services'));
79
98
  console.log(chalk.gray(' cache:update Force update cache to latest version'));
80
99
  console.log(chalk.gray(' cache:clear Delete cache (re-download on next use)\n'));
81
100
  console.log(chalk.white('Examples:'));
82
- console.log(chalk.gray(' # Interactive mode'));
83
101
  console.log(chalk.gray(' launchframe init\n'));
84
- console.log(chalk.gray(' # Non-interactive mode'));
85
- console.log(chalk.gray(' launchframe init --project-name my-saas --tenancy single --user-model b2b\n'));
102
+ console.log(chalk.gray(' # With verbose output'));
103
+ console.log(chalk.gray(' launchframe init --verbose\n'));
86
104
  }
87
105
  }
88
106
 
@@ -4,15 +4,10 @@ const chalk = require('chalk');
4
4
  const { runInitPrompts, runVariantPrompts } = require('../prompts');
5
5
  const { generateProject } = require('../generator');
6
6
  const { checkGitHubAccess, showAccessDeniedMessage } = require('../utils/github-access');
7
- const { ensureCacheReady } = require('../utils/module-cache');
8
-
9
- /**
10
- * Check if current directory is a LaunchFrame project
11
- */
12
- function isLaunchFrameProject() {
13
- const markerPath = path.join(process.cwd(), '.launchframe');
14
- return fs.existsSync(markerPath);
15
- }
7
+ const { ensureCacheReady } = require('../utils/service-cache');
8
+ const { isLaunchFrameProject } = require('../utils/project-helpers');
9
+ const logger = require('../utils/logger');
10
+ const { trackEvent } = require('../utils/telemetry');
16
11
 
17
12
  /**
18
13
  * Check if running in development mode (local) vs production (npm install)
@@ -30,28 +25,30 @@ function isDevMode() {
30
25
  * @param {string} options.userModel - User model: 'b2b' or 'b2b2c' (skips prompt if provided)
31
26
  */
32
27
  async function init(options = {}) {
33
- console.log(chalk.blue.bold('\n🚀 Welcome to LaunchFrame!\n'));
28
+ console.log(chalk.blue.bold('\nLaunchFrame\n'));
34
29
 
35
30
  // Check if in development mode
36
31
  const devMode = isDevMode();
37
-
32
+
38
33
  if (!devMode) {
39
34
  // Production mode: Check GitHub access
40
- console.log(chalk.blue('🔍 Checking repository access...\n'));
41
-
35
+ console.log(chalk.gray('Checking repository access...'));
36
+
42
37
  const accessCheck = await checkGitHubAccess();
43
-
38
+
44
39
  if (!accessCheck.hasAccess) {
45
- // No access - show purchase/setup message
46
40
  showAccessDeniedMessage();
47
- process.exit(1); // Exit with error code
41
+ trackEvent('command_executed', { command: 'init', success: false, error_message: 'access_denied' });
42
+ await new Promise((resolve) => setTimeout(resolve, 100));
43
+ process.exit(1);
48
44
  }
49
-
50
- console.log(chalk.green('Repository access confirmed\n'));
45
+
46
+ console.log(chalk.green('Repository access confirmed\n'));
51
47
  }
48
+
52
49
  // Check if already in a LaunchFrame project
53
50
  if (isLaunchFrameProject()) {
54
- console.error(chalk.red('\n❌ Error: Already in a LaunchFrame project'));
51
+ console.error(chalk.red('Error: Already in a LaunchFrame project'));
55
52
  console.log(chalk.gray('Use other commands to manage your project, or run init from outside the project.\n'));
56
53
  process.exit(1);
57
54
  }
@@ -61,12 +58,10 @@ async function init(options = {}) {
61
58
 
62
59
  // If project name provided via flag, skip prompts
63
60
  if (options.projectName) {
64
- // Validate project name format
65
61
  if (!/^[a-z0-9-]+$/.test(options.projectName)) {
66
62
  throw new Error('Project name must contain only lowercase letters, numbers, and hyphens');
67
63
  }
68
64
 
69
- // Auto-generate display name from slug
70
65
  const projectDisplayName = options.projectName
71
66
  .split('-')
72
67
  .map(word => word.charAt(0).toUpperCase() + word.slice(1))
@@ -82,91 +77,81 @@ async function init(options = {}) {
82
77
  projectNameCamel: options.projectName.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())
83
78
  };
84
79
 
85
- console.log(chalk.gray(`Using project name: ${options.projectName}`));
86
- console.log(chalk.gray(`Using display name: ${projectDisplayName}`));
87
- console.log(chalk.gray(`Using description: ${projectDescription}\n`));
80
+ logger.detail(`Project name: ${options.projectName}`);
81
+ logger.detail(`Display name: ${projectDisplayName}`);
82
+ logger.detail(`Description: ${projectDescription}`);
88
83
  } else {
89
- // Get user inputs via interactive prompts
90
84
  answers = await runInitPrompts();
91
85
  }
92
86
 
93
87
  // Get variant selections (multi-tenancy, B2B vs B2B2C)
94
88
  let variantChoices;
95
-
96
- // If both flags provided, skip variant prompts
89
+
97
90
  if (options.tenancy && options.userModel) {
98
- // Validate tenancy value
99
91
  if (!['single', 'multi'].includes(options.tenancy)) {
100
92
  throw new Error('Invalid --tenancy value. Must be "single" or "multi"');
101
93
  }
102
-
103
- // Validate userModel value
94
+
104
95
  if (!['b2b', 'b2b2c'].includes(options.userModel)) {
105
96
  throw new Error('Invalid --user-model value. Must be "b2b" or "b2b2c"');
106
97
  }
107
-
108
- // Convert short flag values to full variant names
98
+
109
99
  const tenancyMap = {
110
100
  'single': 'single-tenant',
111
101
  'multi': 'multi-tenant'
112
102
  };
113
-
103
+
114
104
  variantChoices = {
115
105
  tenancy: tenancyMap[options.tenancy],
116
106
  userModel: options.userModel
117
107
  };
118
-
119
- console.log(chalk.gray(`Using tenancy: ${options.tenancy}`));
120
- console.log(chalk.gray(`Using user model: ${options.userModel}\n`));
108
+
109
+ logger.detail(`Tenancy: ${options.tenancy}`);
110
+ logger.detail(`User model: ${options.userModel}`);
121
111
  } else {
122
- // Run interactive variant prompts
123
112
  variantChoices = await runVariantPrompts();
124
113
  }
125
114
 
126
- // Determine which modules are needed based on variant choices
127
- const requiredModules = [
128
- 'backend',
129
- 'admin-portal',
130
- 'infrastructure',
131
- 'website'
132
- ];
133
-
134
- // Add customers-portal only if B2B2C mode
115
+ // Determine which services are needed
116
+ const requiredServices = ['backend', 'admin-portal', 'infrastructure', 'website'];
117
+
135
118
  if (variantChoices.userModel === 'b2b2c') {
136
- requiredModules.push('customers-portal');
119
+ requiredServices.push('customers-portal');
137
120
  }
138
121
 
139
- // Determine template source (dev mode = local, production = cache)
122
+ // Determine template source
140
123
  let templateRoot;
141
-
124
+
142
125
  if (devMode) {
143
- // Dev mode: Use local modules directory
144
- templateRoot = path.resolve(__dirname, '../../../modules');
145
- console.log(chalk.gray(`[DEV MODE] Using local modules: ${templateRoot}\n`));
126
+ templateRoot = path.resolve(__dirname, '../../../services');
127
+ logger.detail(`[DEV MODE] Using local services: ${templateRoot}`);
146
128
  } else {
147
- // Production mode: Use cache
148
129
  try {
149
- templateRoot = await ensureCacheReady(requiredModules);
130
+ templateRoot = await ensureCacheReady(requiredServices);
150
131
  } catch (error) {
151
- console.error(chalk.red(`\n❌ Error: ${error.message}\n`));
132
+ console.error(chalk.red(`Error: ${error.message}\n`));
152
133
  process.exit(1);
153
134
  }
154
135
  }
155
136
 
156
- // Generate project with variant selections
157
- console.log(chalk.yellow('\n⚙️ Generating project...\n'));
137
+ // Generate project
138
+ console.log(chalk.white('\nGenerating project...\n'));
158
139
  await generateProject(answers, variantChoices, templateRoot);
159
140
 
160
- console.log(chalk.green.bold('\n✅ Project generated successfully!\n'));
141
+ trackEvent('command_executed', {
142
+ command: 'init',
143
+ success: true,
144
+ tenancy: variantChoices.tenancy,
145
+ user_model: variantChoices.userModel
146
+ });
147
+
148
+ console.log(chalk.green.bold('\nProject created successfully!\n'));
161
149
  console.log(chalk.white('Next steps:'));
162
- console.log(chalk.white(` cd ${answers.projectName}`));
163
- console.log(chalk.white(' launchframe docker:up # Start all services\n'));
164
- console.log(chalk.gray('Optional:'));
165
- console.log(chalk.gray(' # Review and customize infrastructure/.env if needed'));
166
- console.log(chalk.gray(' launchframe docker:build # Rebuild images after changes\n'));
150
+ console.log(chalk.gray(` cd ${answers.projectName}`));
151
+ console.log(chalk.gray(' launchframe docker:up\n'));
167
152
 
168
153
  } catch (error) {
169
- console.error(chalk.red('\n❌ Error:'), error.message);
154
+ console.error(chalk.red('Error:'), error.message);
170
155
  process.exit(1);
171
156
  }
172
157
  }
@@ -0,0 +1,40 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const { execSync } = require('child_process');
5
+ const { requireProject } = require('../utils/project-helpers');
6
+
7
+ async function migrateCreate() {
8
+ requireProject();
9
+
10
+ const infrastructurePath = path.join(process.cwd(), 'infrastructure');
11
+
12
+ if (!fs.existsSync(infrastructurePath)) {
13
+ console.error(chalk.red('\n❌ Error: infrastructure/ directory not found'));
14
+ console.log(chalk.gray('Make sure you are in the root of your LaunchFrame project.\n'));
15
+ process.exit(1);
16
+ }
17
+
18
+ const migrationName = process.argv[3];
19
+
20
+ if (!migrationName) {
21
+ console.error(chalk.red('\n❌ Error: migration name is required'));
22
+ console.log(chalk.gray('Usage: launchframe migrate:create <name>\n'));
23
+ process.exit(1);
24
+ }
25
+
26
+ console.log(chalk.blue.bold(`\n🗄️ Creating migration: ${migrationName}\n`));
27
+
28
+ try {
29
+ execSync(`docker compose -f docker-compose.yml -f docker-compose.dev.yml exec backend npm run migration:create -- ${migrationName}`, {
30
+ cwd: infrastructurePath,
31
+ stdio: 'inherit'
32
+ });
33
+ console.log(chalk.green.bold('\n✅ Migration created successfully.\n'));
34
+ } catch (error) {
35
+ console.error(chalk.red('\n❌ Error creating migration:'), error.message);
36
+ process.exit(1);
37
+ }
38
+ }
39
+
40
+ module.exports = { migrateCreate };
@@ -0,0 +1,32 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const { execSync } = require('child_process');
5
+ const { requireProject } = require('../utils/project-helpers');
6
+
7
+ async function migrateRevert() {
8
+ requireProject();
9
+
10
+ const infrastructurePath = path.join(process.cwd(), 'infrastructure');
11
+
12
+ if (!fs.existsSync(infrastructurePath)) {
13
+ console.error(chalk.red('\n❌ Error: infrastructure/ directory not found'));
14
+ console.log(chalk.gray('Make sure you are in the root of your LaunchFrame project.\n'));
15
+ process.exit(1);
16
+ }
17
+
18
+ console.log(chalk.blue.bold('\n🗄️ Reverting last database migration\n'));
19
+
20
+ try {
21
+ execSync('docker compose -f docker-compose.yml -f docker-compose.dev.yml exec backend npm run migration:revert', {
22
+ cwd: infrastructurePath,
23
+ stdio: 'inherit'
24
+ });
25
+ console.log(chalk.green.bold('\n✅ Migration reverted successfully.\n'));
26
+ } catch (error) {
27
+ console.error(chalk.red('\n❌ Error reverting migration:'), error.message);
28
+ process.exit(1);
29
+ }
30
+ }
31
+
32
+ module.exports = { migrateRevert };
@@ -0,0 +1,32 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const { execSync } = require('child_process');
5
+ const { requireProject } = require('../utils/project-helpers');
6
+
7
+ async function migrateRun() {
8
+ requireProject();
9
+
10
+ const infrastructurePath = path.join(process.cwd(), 'infrastructure');
11
+
12
+ if (!fs.existsSync(infrastructurePath)) {
13
+ console.error(chalk.red('\n❌ Error: infrastructure/ directory not found'));
14
+ console.log(chalk.gray('Make sure you are in the root of your LaunchFrame project.\n'));
15
+ process.exit(1);
16
+ }
17
+
18
+ console.log(chalk.blue.bold('\n🗄️ Running database migrations\n'));
19
+
20
+ try {
21
+ execSync('docker compose -f docker-compose.yml -f docker-compose.dev.yml exec backend npm run migration:run', {
22
+ cwd: infrastructurePath,
23
+ stdio: 'inherit'
24
+ });
25
+ console.log(chalk.green.bold('\n✅ Migrations completed successfully.\n'));
26
+ } catch (error) {
27
+ console.error(chalk.red('\n❌ Error running migrations:'), error.message);
28
+ process.exit(1);
29
+ }
30
+ }
31
+
32
+ module.exports = { migrateRun };