@launchframe/cli 0.1.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.
@@ -0,0 +1,73 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const { execSync } = require('child_process');
5
+ const { requireProject, getProjectConfig } = require('../utils/project-helpers');
6
+
7
+ /**
8
+ * Start Docker services (all or specific service)
9
+ * @param {string} serviceName - Optional service name to start (e.g., 'docs', 'backend')
10
+ */
11
+ async function dockerUp(serviceName) {
12
+ requireProject();
13
+
14
+ const infrastructurePath = path.join(process.cwd(), 'infrastructure');
15
+
16
+ if (!fs.existsSync(infrastructurePath)) {
17
+ console.error(chalk.red('\nāŒ Error: infrastructure/ directory not found'));
18
+ console.log(chalk.gray('Make sure you are in the root of your LaunchFrame project.\n'));
19
+ process.exit(1);
20
+ }
21
+
22
+ if (serviceName) {
23
+ console.log(chalk.blue.bold(`\nšŸš€ Starting ${serviceName} service\n`));
24
+ console.log(chalk.gray(`Starting ${serviceName} in detached mode...\n`));
25
+ } else {
26
+ console.log(chalk.blue.bold('\nšŸš€ Starting Docker Services\n'));
27
+ console.log(chalk.gray('Starting all services in detached mode...\n'));
28
+ }
29
+
30
+ try {
31
+ const upCommand = serviceName
32
+ ? `docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d ${serviceName}`
33
+ : 'docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d';
34
+
35
+ console.log(chalk.gray(`Running: ${upCommand}\n`));
36
+
37
+ execSync(upCommand, {
38
+ cwd: infrastructurePath,
39
+ stdio: 'inherit'
40
+ });
41
+
42
+ if (serviceName) {
43
+ console.log(chalk.green.bold(`\nāœ… ${serviceName} service started successfully!\n`));
44
+ console.log(chalk.white('Useful commands:'));
45
+ console.log(chalk.gray(` launchframe docker:logs ${serviceName} # View logs`));
46
+ console.log(chalk.gray(` docker-compose -f docker-compose.yml -f docker-compose.dev.yml down ${serviceName} # Stop service\n`));
47
+ } else {
48
+ console.log(chalk.green.bold('\nāœ… All services started successfully!\n'));
49
+ console.log(chalk.white('Services running at:'));
50
+ console.log(chalk.gray(' Backend API: http://localhost:4000'));
51
+ console.log(chalk.gray(' Admin Panel: http://localhost:3001'));
52
+
53
+ // Only show Customers Portal for B2B2C variant
54
+ const config = getProjectConfig();
55
+ if (config.variants && config.variants.userModel === 'b2b2c') {
56
+ console.log(chalk.gray(' Customers Portal: http://localhost:3000'));
57
+ }
58
+
59
+ console.log(chalk.gray(' Marketing Site: http://localhost:8080\n'));
60
+ console.log(chalk.white('Useful commands:'));
61
+ console.log(chalk.gray(' launchframe docker:logs # View logs from all services'));
62
+ console.log(chalk.gray(' launchframe docker:logs backend # View logs from specific service'));
63
+ console.log(chalk.gray(' launchframe docker:down # Stop services (keeps data)'));
64
+ console.log(chalk.gray(' launchframe docker:destroy # Remove all resources\n'));
65
+ }
66
+
67
+ } catch (error) {
68
+ console.error(chalk.red('\nāŒ Error starting services:'), error.message);
69
+ process.exit(1);
70
+ }
71
+ }
72
+
73
+ module.exports = { dockerUp };
@@ -0,0 +1,20 @@
1
+ const chalk = require('chalk');
2
+ const { requireProject } = require('../utils/project-helpers');
3
+
4
+ /**
5
+ * Health check for project
6
+ */
7
+ async function doctor() {
8
+ requireProject();
9
+
10
+ console.log(chalk.blue.bold('\nšŸ” LaunchFrame Health Check\n'));
11
+
12
+ // TODO: Implement health check
13
+ // - Check Docker is running
14
+ // - Verify .env file exists
15
+ // - Check database connection
16
+ // - Verify all services are healthy
17
+ console.log(chalk.gray('Coming soon...'));
18
+ }
19
+
20
+ module.exports = { doctor };
@@ -0,0 +1,79 @@
1
+ const chalk = require('chalk');
2
+ const { isLaunchFrameProject, isWaitlistInstalled } = require('../utils/project-helpers');
3
+
4
+ /**
5
+ * Show help message
6
+ */
7
+ function help() {
8
+ const inProject = isLaunchFrameProject();
9
+
10
+ console.log(chalk.blue.bold('\nšŸš€ LaunchFrame CLI\n'));
11
+ console.log(chalk.white('Usage:'));
12
+ console.log(chalk.gray(' launchframe [command]\n'));
13
+
14
+ if (inProject) {
15
+ console.log(chalk.white('Deployment commands:'));
16
+ console.log(chalk.gray(' deploy:configure Configure production deployment settings'));
17
+ console.log(chalk.gray(' deploy:set-env Configure production environment variables'));
18
+ console.log(chalk.gray(' deploy:init Initialize VPS and build Docker images'));
19
+ console.log(chalk.gray(' deploy:up Start services on VPS\n'));
20
+
21
+ // Conditionally show waitlist commands
22
+ if (isWaitlistInstalled()) {
23
+ console.log(chalk.white('Waitlist commands:'));
24
+ console.log(chalk.gray(' waitlist:deploy Build and deploy waitlist component to VPS'));
25
+ console.log(chalk.gray(' waitlist:up Start waitlist component locally'));
26
+ console.log(chalk.gray(' waitlist:down Stop waitlist component locally'));
27
+ console.log(chalk.gray(' waitlist:logs View waitlist logs from VPS\n'));
28
+ }
29
+ console.log(chalk.white('Local Docker commands:'));
30
+ console.log(chalk.gray(' docker:build Build all Docker images'));
31
+ console.log(chalk.gray(' docker:up [service] Start all services or specific service (detached)'));
32
+ console.log(chalk.gray(' docker:down Stop all services (keeps data)'));
33
+ console.log(chalk.gray(' docker:logs [service] View logs from all services or specific service'));
34
+ console.log(chalk.gray(' docker:destroy Remove all resources (containers, volumes, images)'));
35
+ console.log(chalk.gray(' --force, -f Skip confirmation prompt\n'));
36
+ console.log(chalk.white('Service Management:'));
37
+ console.log(chalk.gray(' service:add <name> Add an optional service to your project'));
38
+ console.log(chalk.gray(' service:list List available services'));
39
+ console.log(chalk.gray(' service:remove <name> Remove installed service\n'));
40
+ console.log(chalk.white('Available Services:'));
41
+ console.log(chalk.gray(' waitlist Coming soon page with email collection\n'));
42
+ console.log(chalk.white('Other commands:'));
43
+ console.log(chalk.gray(' doctor Check project health and configuration'));
44
+ console.log(chalk.gray(' help Show this help message\n'));
45
+ console.log(chalk.white('Examples:'));
46
+ console.log(chalk.gray(' # Deploy full app to production'));
47
+ console.log(chalk.gray(' launchframe deploy:configure'));
48
+ console.log(chalk.gray(' launchframe deploy:set-env'));
49
+ console.log(chalk.gray(' launchframe deploy:init'));
50
+ console.log(chalk.gray(' launchframe deploy:up\n'));
51
+
52
+ // Conditionally show waitlist example
53
+ if (isWaitlistInstalled()) {
54
+ console.log(chalk.gray(' # Deploy waitlist component'));
55
+ console.log(chalk.gray(' launchframe waitlist:deploy'));
56
+ console.log(chalk.gray(' launchframe waitlist:up\n'));
57
+ }
58
+
59
+ console.log(chalk.gray(' # Local development'));
60
+ console.log(chalk.gray(' launchframe docker:up # Start all services'));
61
+ console.log(chalk.gray(' launchframe docker:up docs # Start specific service'));
62
+ console.log(chalk.gray(' launchframe docker:logs backend # View backend logs\n'));
63
+ console.log(chalk.gray(' # Add services'));
64
+ console.log(chalk.gray(' launchframe service:add docs'));
65
+ console.log(chalk.gray(' launchframe docker:up docs # Start the docs service\n'));
66
+ } else {
67
+ console.log(chalk.white('Available commands:'));
68
+ console.log(chalk.gray(' init Initialize a new LaunchFrame project'));
69
+ console.log(chalk.gray(' --project-name <name> Project name (skips prompt)'));
70
+ console.log(chalk.gray(' help Show this help message\n'));
71
+ console.log(chalk.white('Examples:'));
72
+ console.log(chalk.gray(' # Interactive mode'));
73
+ console.log(chalk.gray(' launchframe init\n'));
74
+ console.log(chalk.gray(' # Non-interactive mode'));
75
+ console.log(chalk.gray(' launchframe init --project-name my-saas\n'));
76
+ }
77
+ }
78
+
79
+ module.exports = { help };
@@ -0,0 +1,126 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const { runInitPrompts, runVariantPrompts } = require('../prompts');
5
+ const { generateProject } = require('../generator');
6
+
7
+ /**
8
+ * Check if current directory is a LaunchFrame project
9
+ */
10
+ function isLaunchFrameProject() {
11
+ const markerPath = path.join(process.cwd(), '.launchframe');
12
+ return fs.existsSync(markerPath);
13
+ }
14
+
15
+ /**
16
+ * Check if running in development mode (local) vs production (npm install)
17
+ */
18
+ function isDevMode() {
19
+ // If LAUNCHFRAME_DEV env var is explicitly set, use it
20
+ if (process.env.LAUNCHFRAME_DEV === 'true') {
21
+ return true;
22
+ }
23
+ if (process.env.LAUNCHFRAME_DEV === 'false') {
24
+ return false;
25
+ }
26
+
27
+ // Check if running from node_modules (production) or local directory (dev)
28
+ const scriptPath = __dirname;
29
+ return !scriptPath.includes('node_modules');
30
+ }
31
+
32
+ /**
33
+ * Initialize a new LaunchFrame project
34
+ * @param {Object} options - Command options
35
+ * @param {string} options.projectName - Project name (skips prompt if provided)
36
+ */
37
+ async function init(options = {}) {
38
+ console.log(chalk.blue.bold('\nšŸš€ Welcome to LaunchFrame!\n'));
39
+
40
+ // If running via npm (production), show waitlist message
41
+ if (!isDevMode()) {
42
+ console.log(chalk.white('LaunchFrame is a production-ready B2B SaaS boilerplate that gives you:'));
43
+ console.log(chalk.gray(' • Single VPS deployment ($7-20/mo with Docker + Traefik)'));
44
+ console.log(chalk.gray(' • Variant selection on init (single/multi-tenant, B2B/B2B2C)'));
45
+ console.log(chalk.gray(' • Service registry (add docs, waitlist, admin tools - zero config)'));
46
+ console.log(chalk.gray(' • Full-stack TypeScript (NestJS + React + Next.js)'));
47
+ console.log(chalk.gray(' • Subscriptions + credits (hybrid monetization)'));
48
+ console.log(chalk.gray(' • Feature guard system (tier-based access control)'));
49
+ console.log(chalk.gray(' • API key management + auto-generated docs'));
50
+ console.log(chalk.gray(' • Resilient webhook architecture\n'));
51
+
52
+ console.log(chalk.yellow.bold('šŸ“‹ LaunchFrame is currently in private beta.\n'));
53
+
54
+ console.log(chalk.white('To get early access and be notified when the full version is available:'));
55
+ console.log(chalk.cyan.bold('\n šŸ‘‰ Join the waitlist at https://launchframe.dev\n'));
56
+
57
+ console.log(chalk.gray('Founding members get:'));
58
+ console.log(chalk.gray(' • Exclusive early access'));
59
+ console.log(chalk.gray(' • Lifetime updates'));
60
+ console.log(chalk.gray(' • Priority support'));
61
+ console.log(chalk.gray(' • Special launch pricing\n'));
62
+
63
+ console.log(chalk.white('Questions? Email support@launchframe.dev\n'));
64
+ return;
65
+ }
66
+
67
+ // Dev mode - continue with normal project generation
68
+ // Check if already in a LaunchFrame project
69
+ if (isLaunchFrameProject()) {
70
+ console.error(chalk.red('\nāŒ Error: Already in a LaunchFrame project'));
71
+ console.log(chalk.gray('Use other commands to manage your project, or run init from outside the project.\n'));
72
+ process.exit(1);
73
+ }
74
+
75
+ try {
76
+ let answers;
77
+
78
+ // If project name provided via flag, skip prompts
79
+ if (options.projectName) {
80
+ // Validate project name format
81
+ if (!/^[a-z0-9-]+$/.test(options.projectName)) {
82
+ throw new Error('Project name must contain only lowercase letters, numbers, and hyphens');
83
+ }
84
+
85
+ // Auto-generate display name from slug
86
+ const projectDisplayName = options.projectName
87
+ .split('-')
88
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
89
+ .join(' ');
90
+
91
+ answers = {
92
+ projectName: options.projectName,
93
+ projectDisplayName: projectDisplayName,
94
+ projectNameUpper: options.projectName.toUpperCase().replace(/-/g, '_'),
95
+ projectNameCamel: options.projectName.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())
96
+ };
97
+
98
+ console.log(chalk.gray(`Using project name: ${options.projectName}`));
99
+ console.log(chalk.gray(`Using display name: ${projectDisplayName}\n`));
100
+ } else {
101
+ // Get user inputs via interactive prompts
102
+ answers = await runInitPrompts();
103
+ }
104
+
105
+ // Get variant selections (multi-tenancy, B2B vs B2B2C)
106
+ const variantChoices = await runVariantPrompts();
107
+
108
+ // Generate project with variant selections
109
+ console.log(chalk.yellow('\nāš™ļø Generating project...\n'));
110
+ await generateProject(answers, variantChoices);
111
+
112
+ console.log(chalk.green.bold('\nāœ… Project generated successfully!\n'));
113
+ console.log(chalk.white('Next steps:'));
114
+ console.log(chalk.white(` cd ${answers.projectName}`));
115
+ console.log(chalk.white(' launchframe docker:up # Start all services\n'));
116
+ console.log(chalk.gray('Optional:'));
117
+ console.log(chalk.gray(' # Review and customize infrastructure/.env if needed'));
118
+ console.log(chalk.gray(' launchframe docker:build # Rebuild images after changes\n'));
119
+
120
+ } catch (error) {
121
+ console.error(chalk.red('\nāŒ Error:'), error.message);
122
+ process.exit(1);
123
+ }
124
+ }
125
+
126
+ module.exports = { init };