@launchframe/cli 1.0.0-beta.15 ā 1.0.0-beta.18
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/deploy-build.js +137 -0
- package/src/commands/deploy-set-env.js +1 -0
- package/src/commands/deploy-up.js +2 -2
- package/src/commands/help.js +2 -1
- package/src/index.js +4 -0
- package/src/services/variant-config.js +1 -7
- package/src/utils/docker-helper.js +1 -0
- package/src/utils/variant-processor.js +3 -1
package/package.json
CHANGED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { exec } = require('child_process');
|
|
3
|
+
const { promisify } = require('util');
|
|
4
|
+
const ora = require('ora');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { requireProject, getProjectConfig } = require('../utils/project-helpers');
|
|
7
|
+
const { checkDockerRunning, loginToGHCR, buildFullAppImages, buildAndPushImage } = require('../utils/docker-helper');
|
|
8
|
+
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build, push, and deploy Docker images
|
|
13
|
+
* @param {string} [serviceName] - Optional specific service to build (e.g., 'backend', 'admin-portal')
|
|
14
|
+
*/
|
|
15
|
+
async function deployBuild(serviceName) {
|
|
16
|
+
requireProject();
|
|
17
|
+
|
|
18
|
+
const serviceLabel = serviceName ? `(${serviceName})` : '(all services)';
|
|
19
|
+
console.log(chalk.blue.bold(`\nšØ LaunchFrame Build & Deploy ${serviceLabel}\n`));
|
|
20
|
+
|
|
21
|
+
const config = getProjectConfig();
|
|
22
|
+
const projectRoot = process.cwd();
|
|
23
|
+
|
|
24
|
+
// Validate deployment is configured
|
|
25
|
+
if (!config.deployConfigured || !config.deployment) {
|
|
26
|
+
console.log(chalk.red('ā Error: Deployment not configured yet\n'));
|
|
27
|
+
console.log(chalk.gray('Run this command first:'));
|
|
28
|
+
console.log(chalk.white(' launchframe deploy:configure\n'));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { vpsHost, vpsUser, vpsAppFolder, githubOrg } = config.deployment;
|
|
33
|
+
const { projectName, installedServices } = config;
|
|
34
|
+
const envProdPath = path.join(projectRoot, 'infrastructure', '.env.prod');
|
|
35
|
+
|
|
36
|
+
// Step 1: Check Docker is running
|
|
37
|
+
console.log(chalk.yellow('š³ Step 1: Checking Docker...\n'));
|
|
38
|
+
|
|
39
|
+
const dockerSpinner = ora('Checking Docker...').start();
|
|
40
|
+
|
|
41
|
+
const dockerRunning = await checkDockerRunning();
|
|
42
|
+
if (!dockerRunning) {
|
|
43
|
+
dockerSpinner.fail('Docker is not running');
|
|
44
|
+
console.log(chalk.red('\nā Please start Docker and try again.\n'));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
dockerSpinner.succeed('Docker is running');
|
|
49
|
+
|
|
50
|
+
// Step 2: Login to GHCR
|
|
51
|
+
console.log(chalk.yellow('\nš Step 2: Logging in to GitHub Container Registry...\n'));
|
|
52
|
+
|
|
53
|
+
const ghcrToken = config.deployment.ghcrToken;
|
|
54
|
+
if (!ghcrToken) {
|
|
55
|
+
console.log(chalk.red('ā Error: GHCR token not found in .launchframe config\n'));
|
|
56
|
+
console.log(chalk.gray('Run deploy:configure to set up your GitHub token.\n'));
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await loginToGHCR(githubOrg, ghcrToken);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.log(chalk.red(`\nā ${error.message}\n`));
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Step 3: Build and push images
|
|
68
|
+
console.log(chalk.yellow('\nš¦ Step 3: Building and pushing images...\n'));
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
if (serviceName) {
|
|
72
|
+
// Build specific service
|
|
73
|
+
if (!installedServices.includes(serviceName)) {
|
|
74
|
+
console.log(chalk.red(`ā Service "${serviceName}" not found in installed services.\n`));
|
|
75
|
+
console.log(chalk.gray(`Available services: ${installedServices.join(', ')}\n`));
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const registry = `ghcr.io/${githubOrg}`;
|
|
80
|
+
await buildAndPushImage(
|
|
81
|
+
serviceName,
|
|
82
|
+
path.join(projectRoot, serviceName),
|
|
83
|
+
registry,
|
|
84
|
+
projectName
|
|
85
|
+
);
|
|
86
|
+
console.log(chalk.green.bold(`\nā
${serviceName} built and pushed to GHCR!\n`));
|
|
87
|
+
} else {
|
|
88
|
+
// Build all services
|
|
89
|
+
await buildFullAppImages(projectRoot, projectName, githubOrg, envProdPath, installedServices);
|
|
90
|
+
console.log(chalk.green.bold('\nā
All images built and pushed to GHCR!\n'));
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.log(chalk.red(`\nā Build failed: ${error.message}\n`));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Step 4: Pull images on VPS
|
|
98
|
+
console.log(chalk.yellow('\nš Step 4: Pulling images on VPS...\n'));
|
|
99
|
+
|
|
100
|
+
const pullSpinner = ora('Pulling images on VPS...').start();
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
await execAsync(
|
|
104
|
+
`ssh ${vpsUser}@${vpsHost} "cd ${vpsAppFolder}/infrastructure && docker-compose -f docker-compose.yml -f docker-compose.prod.yml pull"`,
|
|
105
|
+
{ timeout: 600000 } // 10 minutes
|
|
106
|
+
);
|
|
107
|
+
pullSpinner.succeed('Images pulled on VPS');
|
|
108
|
+
} catch (error) {
|
|
109
|
+
pullSpinner.fail('Failed to pull images');
|
|
110
|
+
console.log(chalk.red(`\nā Error: ${error.message}\n`));
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Step 5: Restart services
|
|
115
|
+
console.log(chalk.yellow('\nš Step 5: Restarting services...\n'));
|
|
116
|
+
|
|
117
|
+
const restartSpinner = ora('Restarting services...').start();
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
await execAsync(
|
|
121
|
+
`ssh ${vpsUser}@${vpsHost} "cd ${vpsAppFolder}/infrastructure && docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d"`,
|
|
122
|
+
{ timeout: 300000 } // 5 minutes
|
|
123
|
+
);
|
|
124
|
+
restartSpinner.succeed('Services restarted');
|
|
125
|
+
} catch (error) {
|
|
126
|
+
restartSpinner.fail('Failed to restart services');
|
|
127
|
+
console.log(chalk.red(`\nā Error: ${error.message}\n`));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Success!
|
|
132
|
+
console.log(chalk.green.bold('\nā
Build and deploy complete!\n'));
|
|
133
|
+
|
|
134
|
+
console.log(chalk.gray('Your updated application is now running.\n'));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = { deployBuild };
|
|
@@ -164,6 +164,7 @@ async function deploySetEnv() {
|
|
|
164
164
|
if (config.deployment?.primaryDomain) {
|
|
165
165
|
const domain = config.deployment.primaryDomain;
|
|
166
166
|
const urlReplacements = {
|
|
167
|
+
'PRIMARY_DOMAIN': domain,
|
|
167
168
|
'API_BASE_URL': `https://api.${domain}`,
|
|
168
169
|
'ADMIN_BASE_URL': `https://admin.${domain}`,
|
|
169
170
|
'FRONTEND_BASE_URL': `https://app.${domain}`,
|
|
@@ -106,7 +106,7 @@ async function deployUp() {
|
|
|
106
106
|
|
|
107
107
|
try {
|
|
108
108
|
const { stdout: psOutput } = await execAsync(
|
|
109
|
-
`ssh ${vpsUser}@${vpsHost} "cd ${vpsAppFolder}/infrastructure && docker-compose -f docker-compose.yml ps"`,
|
|
109
|
+
`ssh ${vpsUser}@${vpsHost} "cd ${vpsAppFolder}/infrastructure && docker-compose -f docker-compose.yml -f docker-compose.prod.yml ps"`,
|
|
110
110
|
{ timeout: 30000 }
|
|
111
111
|
);
|
|
112
112
|
|
|
@@ -139,7 +139,7 @@ async function deployUp() {
|
|
|
139
139
|
console.log(chalk.gray(' Just push to GitHub - CI/CD will handle deployment automatically!\n'));
|
|
140
140
|
|
|
141
141
|
console.log(chalk.white('3. Monitor services:'));
|
|
142
|
-
console.log(chalk.gray(` Run: ssh ${vpsUser}@${vpsHost} "cd ${vpsAppFolder}/infrastructure && docker-compose logs -f"\n`));
|
|
142
|
+
console.log(chalk.gray(` Run: ssh ${vpsUser}@${vpsHost} "cd ${vpsAppFolder}/infrastructure && docker-compose -f docker-compose.yml -f docker-compose.prod.yml logs -f"\n`));
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
module.exports = { deployUp };
|
package/src/commands/help.js
CHANGED
|
@@ -18,7 +18,8 @@ function help() {
|
|
|
18
18
|
console.log(chalk.gray(' deploy:configure Configure production deployment settings'));
|
|
19
19
|
console.log(chalk.gray(' deploy:set-env Configure production environment variables'));
|
|
20
20
|
console.log(chalk.gray(' deploy:init Initialize VPS and build Docker images'));
|
|
21
|
-
console.log(chalk.gray(' deploy:up Start services on VPS
|
|
21
|
+
console.log(chalk.gray(' deploy:up Start services on VPS'));
|
|
22
|
+
console.log(chalk.gray(' deploy:build [service] Build, push, and deploy (all or specific service)\n'));
|
|
22
23
|
|
|
23
24
|
// Conditionally show waitlist commands
|
|
24
25
|
if (isWaitlistInstalled()) {
|
package/src/index.js
CHANGED
|
@@ -10,6 +10,7 @@ const { deployConfigure } = require('./commands/deploy-configure');
|
|
|
10
10
|
const { deploySetEnv } = require('./commands/deploy-set-env');
|
|
11
11
|
const { deployInit } = require('./commands/deploy-init');
|
|
12
12
|
const { deployUp } = require('./commands/deploy-up');
|
|
13
|
+
const { deployBuild } = require('./commands/deploy-build');
|
|
13
14
|
const { waitlistDeploy } = require('./commands/waitlist-deploy');
|
|
14
15
|
const { waitlistUp } = require('./commands/waitlist-up');
|
|
15
16
|
const { waitlistDown } = require('./commands/waitlist-down');
|
|
@@ -98,6 +99,9 @@ async function main() {
|
|
|
98
99
|
case 'deploy:up':
|
|
99
100
|
await deployUp();
|
|
100
101
|
break;
|
|
102
|
+
case 'deploy:build':
|
|
103
|
+
await deployBuild(args[1]); // Optional service name
|
|
104
|
+
break;
|
|
101
105
|
case 'waitlist:deploy':
|
|
102
106
|
await waitlistDeploy();
|
|
103
107
|
break;
|
|
@@ -21,7 +21,6 @@ const VARIANT_CONFIG = {
|
|
|
21
21
|
// Complete files/folders to copy
|
|
22
22
|
files: [
|
|
23
23
|
'src/modules/domain/projects', // Entire projects module
|
|
24
|
-
'src/modules/domain/ai/services/project-config.service.ts', // Project config service
|
|
25
24
|
'src/guards/project-ownership.guard.ts', // Project ownership guard (header-based)
|
|
26
25
|
'src/guards/project-param.guard.ts', // Project param guard (route-based)
|
|
27
26
|
'src/modules/users/users.service.ts', // Users service with multi-tenant support
|
|
@@ -30,13 +29,8 @@ const VARIANT_CONFIG = {
|
|
|
30
29
|
],
|
|
31
30
|
|
|
32
31
|
// Code sections to insert into base template files
|
|
32
|
+
// Note: main.ts uses PRIMARY_DOMAIN env var for dynamic CORS - no sections needed
|
|
33
33
|
sections: {
|
|
34
|
-
'src/main.ts': [
|
|
35
|
-
'PROJECT_IMPORTS', // Add project-related imports
|
|
36
|
-
'PROJECT_CUSTOM_DOMAINS', // Add custom domains query
|
|
37
|
-
'PROJECT_CUSTOM_DOMAINS_CORS', // Add custom domains to CORS
|
|
38
|
-
'PROJECT_GUARD' // Add ProjectOwnershipGuard registration
|
|
39
|
-
],
|
|
40
34
|
'src/modules/app/app.module.ts': [
|
|
41
35
|
'PROJECTS_MODULE_IMPORT', // Add ProjectsModule import
|
|
42
36
|
'PROJECTS_MODULE' // Add ProjectsModule to imports array
|
|
@@ -62,7 +62,9 @@ async function processServiceVariant(
|
|
|
62
62
|
const variantConfig = serviceConfig.variants[variantName];
|
|
63
63
|
|
|
64
64
|
if (!variantConfig) {
|
|
65
|
-
|
|
65
|
+
// Silently skip - not every service needs every variant combination
|
|
66
|
+
// (e.g., b2b2c_multi-tenant may only apply to backend, not admin-portal)
|
|
67
|
+
logger.detail(`Skipping ${variantName} (not applicable to this service)`, 3);
|
|
66
68
|
continue;
|
|
67
69
|
}
|
|
68
70
|
|