@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.
- package/README.md +59 -0
- package/package.json +45 -0
- package/src/commands/deploy-configure.js +219 -0
- package/src/commands/deploy-init.js +277 -0
- package/src/commands/deploy-set-env.js +232 -0
- package/src/commands/deploy-up.js +144 -0
- package/src/commands/docker-build.js +44 -0
- package/src/commands/docker-destroy.js +93 -0
- package/src/commands/docker-down.js +44 -0
- package/src/commands/docker-logs.js +69 -0
- package/src/commands/docker-up.js +73 -0
- package/src/commands/doctor.js +20 -0
- package/src/commands/help.js +79 -0
- package/src/commands/init.js +126 -0
- package/src/commands/service.js +569 -0
- package/src/commands/waitlist-deploy.js +231 -0
- package/src/commands/waitlist-down.js +50 -0
- package/src/commands/waitlist-logs.js +55 -0
- package/src/commands/waitlist-up.js +95 -0
- package/src/generator.js +190 -0
- package/src/index.js +158 -0
- package/src/prompts.js +200 -0
- package/src/services/registry.js +48 -0
- package/src/services/variant-config.js +349 -0
- package/src/utils/docker-helper.js +237 -0
- package/src/utils/env-generator.js +88 -0
- package/src/utils/env-validator.js +75 -0
- package/src/utils/file-ops.js +87 -0
- package/src/utils/project-helpers.js +104 -0
- package/src/utils/section-replacer.js +71 -0
- package/src/utils/ssh-helper.js +220 -0
- package/src/utils/variable-replacer.js +95 -0
- package/src/utils/variant-processor.js +313 -0
|
@@ -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 };
|