@launchframe/cli 0.1.11 → 1.0.0-beta.10
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 +44 -9
- package/package.json +5 -5
- package/src/commands/cache.js +102 -0
- package/src/commands/deploy-configure.js +30 -4
- package/src/commands/deploy-init.js +38 -56
- package/src/commands/deploy-set-env.js +68 -91
- package/src/commands/docker-destroy.js +45 -15
- package/src/commands/docker-up.js +42 -16
- package/src/commands/help.js +11 -1
- package/src/commands/init.js +90 -63
- package/src/commands/service.js +71 -41
- package/src/commands/waitlist-deploy.js +2 -2
- package/src/commands/waitlist-logs.js +1 -2
- package/src/commands/waitlist-up.js +50 -15
- package/src/generator.js +16 -7
- package/src/index.js +18 -11
- package/src/prompts.js +12 -0
- package/src/services/registry.js +8 -6
- package/src/services/variant-config.js +146 -48
- package/src/utils/docker-helper.js +66 -44
- package/src/utils/github-access.js +67 -0
- package/src/utils/project-helpers.js +6 -2
- package/src/utils/section-replacer.js +32 -15
- package/src/utils/service-cache.js +274 -0
- package/src/utils/variable-replacer.js +7 -2
- package/src/utils/variant-processor.js +24 -12
package/README.md
CHANGED
|
@@ -2,15 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
> Ship your B2B SaaS to production in hours, not weeks.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**🚀 We're looking for beta testers!** Get free lifetime access to LaunchFrame by joining our [Discord server](https://discord.gg/mH7Xjfeye2) or emailing us at [support@launchframe.dev](mailto:support@launchframe.dev).
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
---
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
LaunchFrame is a production-ready SaaS boilerplate that deploys to a single affordable VPS. Get subscriptions, credits, multi-tenancy, feature gating, and API management out of the box.
|
|
12
12
|
|
|
13
|
-
Here's a sneak peek of the CLI experience:
|
|
14
13
|

|
|
15
14
|
|
|
16
15
|
## What You Get
|
|
@@ -30,14 +29,39 @@ Here's a sneak peek of the CLI experience:
|
|
|
30
29
|
## Installation
|
|
31
30
|
|
|
32
31
|
```bash
|
|
33
|
-
|
|
32
|
+
npm install -g @launchframe/cli
|
|
34
33
|
```
|
|
35
34
|
|
|
36
|
-
##
|
|
35
|
+
## Quick Start
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
Initialize a new LaunchFrame project:
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
```bash
|
|
40
|
+
launchframe init
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Local Development
|
|
44
|
+
|
|
45
|
+
Start the full stack locally with Docker:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
launchframe docker:up
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This spins up all services (backend, admin portal, customer portal, website, database, etc.) with hot-reload enabled. Build your domain logic, customize the UI, and test everything locally.
|
|
52
|
+
|
|
53
|
+
### Deployment
|
|
54
|
+
|
|
55
|
+
When you're ready to deploy to your VPS:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
launchframe deploy:configure # Set up deployment configuration
|
|
59
|
+
launchframe deploy:set-env # Configure environment variables
|
|
60
|
+
launchframe deploy:init # Initialize the VPS
|
|
61
|
+
launchframe deploy:up # Deploy to production
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**[Get started at launchframe.dev](https://launchframe.dev)** | **[Read the docs at docs.launchframe.dev](https://docs.launchframe.dev)**
|
|
41
65
|
|
|
42
66
|
## Why LaunchFrame?
|
|
43
67
|
|
|
@@ -53,6 +77,17 @@ Most SaaS boilerplates give you authentication and a database. LaunchFrame gives
|
|
|
53
77
|
|
|
54
78
|
All tested in production. All ready to customize.
|
|
55
79
|
|
|
80
|
+
## Documentation
|
|
81
|
+
|
|
82
|
+
Full documentation is available at **[docs.launchframe.dev](https://docs.launchframe.dev)**, including:
|
|
83
|
+
|
|
84
|
+
- Getting started guides
|
|
85
|
+
- Architecture overview
|
|
86
|
+
- Deployment instructions
|
|
87
|
+
- API reference
|
|
88
|
+
- Feature customization guides
|
|
89
|
+
- Multi-tenancy patterns
|
|
90
|
+
|
|
56
91
|
## License
|
|
57
92
|
|
|
58
93
|
MIT
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@launchframe/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-beta.10",
|
|
4
4
|
"description": "Production-ready B2B SaaS boilerplate with subscriptions, credits, and multi-tenancy",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"launchframe": "
|
|
7
|
+
"launchframe": "src/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node src/index.js"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"homepage": "https://launchframe.dev",
|
|
29
29
|
"repository": {
|
|
30
30
|
"type": "git",
|
|
31
|
-
"url": "https://github.com/launchframe/cli"
|
|
31
|
+
"url": "git+https://github.com/launchframe/cli.git"
|
|
32
32
|
},
|
|
33
33
|
"bugs": {
|
|
34
34
|
"url": "https://github.com/launchframe/cli/issues"
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"inquirer": "^8.2.5",
|
|
44
43
|
"chalk": "^4.1.2",
|
|
45
44
|
"fs-extra": "^11.1.1",
|
|
46
|
-
"glob": "^10.3.10"
|
|
45
|
+
"glob": "^10.3.10",
|
|
46
|
+
"inquirer": "^8.2.5"
|
|
47
47
|
}
|
|
48
48
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { clearCache, getCacheInfo } = require('../utils/service-cache');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Clear service cache
|
|
6
|
+
*/
|
|
7
|
+
async function cacheClear() {
|
|
8
|
+
console.log(chalk.yellow('\n⚠️ This will delete all cached services'));
|
|
9
|
+
console.log(chalk.gray('You will need to re-download on next init or service:add\n'));
|
|
10
|
+
|
|
11
|
+
const inquirer = require('inquirer');
|
|
12
|
+
const { confirmed } = await inquirer.prompt([{
|
|
13
|
+
type: 'confirm',
|
|
14
|
+
name: 'confirmed',
|
|
15
|
+
message: 'Continue with cache clear?',
|
|
16
|
+
default: false
|
|
17
|
+
}]);
|
|
18
|
+
|
|
19
|
+
if (!confirmed) {
|
|
20
|
+
console.log('Cancelled');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
await clearCache();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Show cache information
|
|
29
|
+
*/
|
|
30
|
+
async function cacheInfo() {
|
|
31
|
+
const info = await getCacheInfo();
|
|
32
|
+
|
|
33
|
+
console.log(chalk.blue('\n📦 Service Cache Information\n'));
|
|
34
|
+
|
|
35
|
+
console.log(chalk.white('Location:'));
|
|
36
|
+
console.log(chalk.gray(` ${info.path}\n`));
|
|
37
|
+
|
|
38
|
+
if (!info.exists) {
|
|
39
|
+
console.log(chalk.yellow('Status: Not initialized'));
|
|
40
|
+
console.log(chalk.gray('Cache will be created on first use\n'));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(chalk.green('Status: Active\n'));
|
|
45
|
+
|
|
46
|
+
if (info.size !== undefined) {
|
|
47
|
+
const sizeMB = (info.size / 1024 / 1024).toFixed(2);
|
|
48
|
+
console.log(chalk.white('Size:'));
|
|
49
|
+
console.log(chalk.gray(` ${sizeMB} MB\n`));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (info.lastUpdate) {
|
|
53
|
+
console.log(chalk.white('Last Updated:'));
|
|
54
|
+
console.log(chalk.gray(` ${info.lastUpdate.toLocaleString()}\n`));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (info.services && info.services.length > 0) {
|
|
58
|
+
console.log(chalk.white('Cached Services:'));
|
|
59
|
+
info.services.forEach(mod => {
|
|
60
|
+
console.log(chalk.gray(` • ${mod}`));
|
|
61
|
+
});
|
|
62
|
+
console.log('');
|
|
63
|
+
} else {
|
|
64
|
+
console.log(chalk.gray('No services cached yet\n'));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(chalk.gray('Commands:'));
|
|
68
|
+
console.log(chalk.gray(' launchframe cache:clear - Delete cache'));
|
|
69
|
+
console.log(chalk.gray(' launchframe cache:update - Force update\n'));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Force update cache
|
|
74
|
+
*/
|
|
75
|
+
async function cacheUpdate() {
|
|
76
|
+
const { ensureCacheReady, getCacheInfo } = require('../utils/service-cache');
|
|
77
|
+
|
|
78
|
+
console.log(chalk.blue('\n🔄 Forcing cache update...\n'));
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const info = await getCacheInfo();
|
|
82
|
+
const currentServices = info.services || [];
|
|
83
|
+
|
|
84
|
+
if (currentServices.length === 0) {
|
|
85
|
+
console.log(chalk.yellow('No services in cache yet. Use init or service:add to populate.\n'));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Update cache with current services
|
|
90
|
+
await ensureCacheReady(currentServices);
|
|
91
|
+
console.log(chalk.green('\n✓ Cache updated successfully\n'));
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error(chalk.red(`\n❌ Failed to update cache: ${error.message}\n`));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
cacheClear,
|
|
100
|
+
cacheInfo,
|
|
101
|
+
cacheUpdate
|
|
102
|
+
};
|
|
@@ -61,9 +61,35 @@ async function deployConfigure() {
|
|
|
61
61
|
// Files that need template variable replacement
|
|
62
62
|
const filesToUpdate = [
|
|
63
63
|
'infrastructure/.env',
|
|
64
|
-
'infrastructure/.env.example'
|
|
64
|
+
'infrastructure/.env.example',
|
|
65
|
+
'infrastructure/docker-compose.yml',
|
|
66
|
+
'infrastructure/docker-compose.dev.yml',
|
|
67
|
+
'infrastructure/docker-compose.prod.yml',
|
|
68
|
+
'infrastructure/traefik.yml',
|
|
69
|
+
'backend/src/main.ts',
|
|
70
|
+
'admin-portal/.env.example',
|
|
71
|
+
'admin-portal/public/env-config.js',
|
|
72
|
+
'admin-portal/src/config/runtime.ts',
|
|
73
|
+
'admin-portal/src/config/pageMetadata.ts',
|
|
74
|
+
'admin-portal/src/App.tsx',
|
|
75
|
+
'admin-portal/src/components/common/PageTitle.tsx',
|
|
76
|
+
'admin-portal/src/sentry.tsx',
|
|
65
77
|
];
|
|
66
78
|
|
|
79
|
+
if (config.variants.tenancy === 'multi-tenant') {
|
|
80
|
+
filesToUpdate.push(
|
|
81
|
+
'admin-portal/src/pages/FirstProject.tsx',
|
|
82
|
+
'admin-portal/src/components/projects/NewProject.tsx',
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (config.variants.userModel === 'b2b2c') {
|
|
87
|
+
filesToUpdate.push(
|
|
88
|
+
'admin-portal/src/components/settings/CustomDomain.tsx',
|
|
89
|
+
'customers-portal/src/App.tsx'
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
67
93
|
const projectRoot = process.cwd();
|
|
68
94
|
let filesUpdated = 0;
|
|
69
95
|
|
|
@@ -134,14 +160,14 @@ async function deployConfigure() {
|
|
|
134
160
|
|
|
135
161
|
const updatedConfig = {
|
|
136
162
|
...config,
|
|
137
|
-
primaryDomain: deployAnswers.primaryDomain,
|
|
138
|
-
githubOrg: deployAnswers.githubOrg,
|
|
139
|
-
vpsAppFolder: deployAnswers.vpsAppFolder,
|
|
140
163
|
deployConfigured: true,
|
|
141
164
|
deployment: {
|
|
142
165
|
adminEmail: deployAnswers.adminEmail,
|
|
143
166
|
vpsHost: deployAnswers.vpsHost,
|
|
144
167
|
vpsUser: deployAnswers.vpsUser,
|
|
168
|
+
vpsAppFolder: deployAnswers.vpsAppFolder,
|
|
169
|
+
primaryDomain: deployAnswers.primaryDomain,
|
|
170
|
+
githubOrg: deployAnswers.githubOrg,
|
|
145
171
|
ghcrToken: deployAnswers.ghcrToken,
|
|
146
172
|
configuredAt: new Date().toISOString()
|
|
147
173
|
}
|
|
@@ -9,13 +9,11 @@ const {
|
|
|
9
9
|
checkSSHKeys,
|
|
10
10
|
executeSSH,
|
|
11
11
|
copyFileToVPS,
|
|
12
|
-
copyDirectoryToVPS
|
|
13
|
-
checkRepoPrivacy,
|
|
14
|
-
showDeployKeyInstructions
|
|
12
|
+
copyDirectoryToVPS
|
|
15
13
|
} = require('../utils/ssh-helper');
|
|
16
14
|
|
|
17
15
|
/**
|
|
18
|
-
* Initial VPS setup -
|
|
16
|
+
* Initial VPS setup - copy infrastructure files and configure environment
|
|
19
17
|
*/
|
|
20
18
|
async function deployInit() {
|
|
21
19
|
requireProject();
|
|
@@ -119,8 +117,9 @@ async function deployInit() {
|
|
|
119
117
|
// Login to GHCR
|
|
120
118
|
await loginToGHCR(githubOrg, ghcrToken);
|
|
121
119
|
|
|
122
|
-
// Build full-app images
|
|
123
|
-
|
|
120
|
+
// Build full-app images (only for installed services)
|
|
121
|
+
const installedServices = config.installedServices || ['backend', 'admin-portal', 'website'];
|
|
122
|
+
await buildFullAppImages(projectRoot, projectName, githubOrg, envProdPath, installedServices);
|
|
124
123
|
|
|
125
124
|
console.log(chalk.green.bold('\n✅ All images built and pushed to GHCR!\n'));
|
|
126
125
|
} catch (error) {
|
|
@@ -137,57 +136,40 @@ async function deployInit() {
|
|
|
137
136
|
}
|
|
138
137
|
|
|
139
138
|
|
|
140
|
-
// Step 4:
|
|
141
|
-
console.log(chalk.yellow('
|
|
139
|
+
// Step 4: Create app directory and copy infrastructure files
|
|
140
|
+
console.log(chalk.yellow('📦 Step 4: Setting up application on VPS...\n'));
|
|
142
141
|
|
|
143
|
-
const
|
|
144
|
-
if (repoCheck.isPrivate) {
|
|
145
|
-
console.log(chalk.yellow('⚠️ Repository appears to be private or inaccessible\n'));
|
|
146
|
-
showDeployKeyInstructions(vpsUser, vpsHost, githubOrg, projectName);
|
|
147
|
-
process.exit(1);
|
|
148
|
-
}
|
|
142
|
+
const setupSpinner = ora('Creating app directory...').start();
|
|
149
143
|
|
|
150
|
-
|
|
144
|
+
try {
|
|
145
|
+
// Create infrastructure directory on VPS
|
|
146
|
+
await executeSSH(vpsUser, vpsHost, `mkdir -p ${vpsAppFolder}/infrastructure`);
|
|
147
|
+
|
|
148
|
+
setupSpinner.text = 'Copying infrastructure files to VPS...';
|
|
151
149
|
|
|
152
|
-
|
|
153
|
-
|
|
150
|
+
// Copy entire infrastructure directory to VPS
|
|
151
|
+
const infrastructurePath = path.join(projectRoot, 'infrastructure');
|
|
152
|
+
await copyDirectoryToVPS(infrastructurePath, vpsUser, vpsHost, `${vpsAppFolder}/infrastructure`);
|
|
154
153
|
|
|
155
|
-
|
|
154
|
+
setupSpinner.succeed('Infrastructure files copied to VPS');
|
|
155
|
+
} catch (error) {
|
|
156
|
+
setupSpinner.fail('Failed to copy infrastructure files');
|
|
157
|
+
console.log(chalk.red(`\n❌ Error: ${error.message}\n`));
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
156
160
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
await executeSSH(vpsUser, vpsHost, `mkdir -p ${vpsAppFolder}`);
|
|
160
|
-
cloneSpinner.text = 'Cloning repository...';
|
|
161
|
-
|
|
162
|
-
// Check if directory is empty or has .git
|
|
163
|
-
const { stdout: lsOutput } = await executeSSH(vpsUser, vpsHost, `ls -A ${vpsAppFolder}`);
|
|
164
|
-
|
|
165
|
-
if (lsOutput.trim()) {
|
|
166
|
-
// Directory not empty
|
|
167
|
-
const { stdout: gitCheck } = await executeSSH(vpsUser, vpsHost, `test -d ${vpsAppFolder}/.git && echo "exists" || echo "missing"`);
|
|
168
|
-
|
|
169
|
-
if (gitCheck.trim() === 'exists') {
|
|
170
|
-
cloneSpinner.text = 'Repository already cloned, pulling latest changes...';
|
|
171
|
-
await executeSSH(vpsUser, vpsHost, `cd ${vpsAppFolder} && git pull origin main || git pull origin master`);
|
|
172
|
-
} else {
|
|
173
|
-
cloneSpinner.warn('Directory not empty and not a git repository');
|
|
174
|
-
console.log(chalk.yellow(`\n⚠️ Warning: ${vpsAppFolder} exists but is not a git repository\n`));
|
|
175
|
-
console.log(chalk.gray('Please clean up the directory or choose a different path.\n'));
|
|
176
|
-
process.exit(1);
|
|
177
|
-
}
|
|
178
|
-
} else {
|
|
179
|
-
// Clone fresh
|
|
180
|
-
await executeSSH(
|
|
181
|
-
vpsUser,
|
|
182
|
-
vpsHost,
|
|
183
|
-
`git clone https://github.com/${githubOrg}/${projectName}.git ${vpsAppFolder}`,
|
|
184
|
-
{ timeout: 180000 } // 3 minutes for clone
|
|
185
|
-
);
|
|
186
|
-
}
|
|
161
|
+
// Create symlink for docker-compose.override.yml -> docker-compose.prod.yml
|
|
162
|
+
const symlinkSpinner = ora('Creating docker-compose.override.yml symlink...').start();
|
|
187
163
|
|
|
188
|
-
|
|
164
|
+
try {
|
|
165
|
+
await executeSSH(
|
|
166
|
+
vpsUser,
|
|
167
|
+
vpsHost,
|
|
168
|
+
`cd ${vpsAppFolder}/infrastructure && ln -sf docker-compose.prod.yml docker-compose.override.yml`
|
|
169
|
+
);
|
|
170
|
+
symlinkSpinner.succeed('Docker Compose override symlink created');
|
|
189
171
|
} catch (error) {
|
|
190
|
-
|
|
172
|
+
symlinkSpinner.fail('Failed to create symlink');
|
|
191
173
|
console.log(chalk.red(`\n❌ Error: ${error.message}\n`));
|
|
192
174
|
process.exit(1);
|
|
193
175
|
}
|
|
@@ -223,23 +205,23 @@ async function deployInit() {
|
|
|
223
205
|
// If error, waitlist probably not running - continue
|
|
224
206
|
}
|
|
225
207
|
|
|
226
|
-
// Step
|
|
227
|
-
console.log(chalk.yellow('\n📄 Step
|
|
208
|
+
// Step 5: Copy .env.prod to VPS (overwrites .env copied from infrastructure/)
|
|
209
|
+
console.log(chalk.yellow('\n📄 Step 5: Configuring production environment...\n'));
|
|
228
210
|
|
|
229
211
|
const envSpinner = ora('Copying .env.prod to VPS...').start();
|
|
230
212
|
|
|
231
213
|
try {
|
|
232
214
|
const remoteEnvPath = `${vpsAppFolder}/infrastructure/.env`;
|
|
233
215
|
await copyFileToVPS(envProdPath, vpsUser, vpsHost, remoteEnvPath);
|
|
234
|
-
envSpinner.succeed('.env.prod copied successfully');
|
|
216
|
+
envSpinner.succeed('.env.prod copied as .env successfully');
|
|
235
217
|
} catch (error) {
|
|
236
218
|
envSpinner.fail('Failed to copy .env.prod');
|
|
237
219
|
console.log(chalk.red(`\n❌ Error: ${error.message}\n`));
|
|
238
220
|
process.exit(1);
|
|
239
221
|
}
|
|
240
222
|
|
|
241
|
-
// Step
|
|
242
|
-
console.log(chalk.yellow('\n🐳 Step
|
|
223
|
+
// Step 6: Pull Docker images
|
|
224
|
+
console.log(chalk.yellow('\n🐳 Step 6: Pulling Docker images...\n'));
|
|
243
225
|
console.log(chalk.gray('This may take several minutes...\n'));
|
|
244
226
|
|
|
245
227
|
const dockerSpinner = ora('Pulling Docker images...').start();
|
|
@@ -266,7 +248,7 @@ async function deployInit() {
|
|
|
266
248
|
console.log(chalk.green.bold('\n✅ Initial VPS setup complete!\n'));
|
|
267
249
|
|
|
268
250
|
console.log(chalk.white('Summary:'));
|
|
269
|
-
console.log(chalk.gray(` -
|
|
251
|
+
console.log(chalk.gray(` - Infrastructure copied to: ${vpsAppFolder}`));
|
|
270
252
|
console.log(chalk.gray(' - Production .env configured'));
|
|
271
253
|
console.log(chalk.gray(' - Docker images pulled\n'));
|
|
272
254
|
|
|
@@ -75,77 +75,65 @@ async function deploySetEnv() {
|
|
|
75
75
|
const bullAdminToken = generateSecret(32);
|
|
76
76
|
|
|
77
77
|
const answers = await inquirer.prompt([
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
type: 'input',
|
|
138
|
-
name: 'googleClientId',
|
|
139
|
-
message: 'Google OAuth Client ID:',
|
|
140
|
-
default: ''
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
type: 'input',
|
|
144
|
-
name: 'googleClientSecret',
|
|
145
|
-
message: 'Google OAuth Client Secret:',
|
|
146
|
-
default: ''
|
|
147
|
-
}
|
|
148
|
-
]);
|
|
78
|
+
{
|
|
79
|
+
type: 'password',
|
|
80
|
+
name: 'dbPassword',
|
|
81
|
+
message: 'Database password:',
|
|
82
|
+
default: dbPassword,
|
|
83
|
+
mask: '*'
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: 'password',
|
|
87
|
+
name: 'redisPassword',
|
|
88
|
+
message: 'Redis password:',
|
|
89
|
+
default: redisPassword,
|
|
90
|
+
mask: '*'
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
type: 'password',
|
|
94
|
+
name: 'jwtSecret',
|
|
95
|
+
message: 'JWT secret:',
|
|
96
|
+
default: jwtSecret,
|
|
97
|
+
mask: '*'
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
type: 'password',
|
|
101
|
+
name: 'bullAdminToken',
|
|
102
|
+
message: 'Bull Admin token:',
|
|
103
|
+
default: bullAdminToken,
|
|
104
|
+
mask: '*'
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
type: 'input',
|
|
108
|
+
name: 'resendApiKey',
|
|
109
|
+
message: 'Resend API key (email):',
|
|
110
|
+
default: ''
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
type: 'input',
|
|
114
|
+
name: 'polarAccessToken',
|
|
115
|
+
message: 'Polar Access Token:',
|
|
116
|
+
default: ''
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
type: 'input',
|
|
120
|
+
name: 'polarWebhookSecret',
|
|
121
|
+
message: 'Polar Webhook Secret:',
|
|
122
|
+
default: ''
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
type: 'input',
|
|
126
|
+
name: 'googleClientId',
|
|
127
|
+
message: 'Google OAuth Client ID:',
|
|
128
|
+
default: ''
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
type: 'input',
|
|
132
|
+
name: 'googleClientSecret',
|
|
133
|
+
message: 'Google OAuth Client Secret:',
|
|
134
|
+
default: ''
|
|
135
|
+
}
|
|
136
|
+
]);
|
|
149
137
|
|
|
150
138
|
// Read current .env.prod content
|
|
151
139
|
let envContent = await fs.readFile(envProdPath, 'utf8');
|
|
@@ -153,26 +141,15 @@ async function deploySetEnv() {
|
|
|
153
141
|
// Replace values based on deployment mode
|
|
154
142
|
const replacements = {};
|
|
155
143
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
replacements['REDIS_PASSWORD'] = answers.redisPassword;
|
|
166
|
-
replacements['JWT_SECRET'] = answers.jwtSecret;
|
|
167
|
-
replacements['BULL_ADMIN_TOKEN'] = answers.bullAdminToken;
|
|
168
|
-
replacements['RESEND_API_KEY'] = answers.resendApiKey || 're_your_resend_api_key';
|
|
169
|
-
replacements['OPENAI_API_KEY'] = answers.openaiApiKey || 'sk-your_openai_api_key';
|
|
170
|
-
replacements['CLAUDE_API_KEY'] = answers.claudeApiKey || 'sk-ant-your_claude_api_key';
|
|
171
|
-
replacements['POLAR_ACCESS_TOKEN'] = answers.polarAccessToken || 'polar_oat_your_token';
|
|
172
|
-
replacements['POLAR_WEBHOOK_SECRET'] = answers.polarWebhookSecret || 'polar_whs_your_secret';
|
|
173
|
-
replacements['GOOGLE_CLIENT_ID'] = answers.googleClientId || 'YOUR_GOOGLE_CLIENT_ID';
|
|
174
|
-
replacements['GOOGLE_CLIENT_SECRET'] = answers.googleClientSecret || 'YOUR_GOOGLE_CLIENT_SECRET';
|
|
175
|
-
}
|
|
144
|
+
replacements['DB_PASSWORD'] = answers.dbPassword;
|
|
145
|
+
replacements['REDIS_PASSWORD'] = answers.redisPassword;
|
|
146
|
+
replacements['JWT_SECRET'] = answers.jwtSecret;
|
|
147
|
+
replacements['BULL_ADMIN_TOKEN'] = answers.bullAdminToken;
|
|
148
|
+
replacements['RESEND_API_KEY'] = answers.resendApiKey || 're_your_resend_api_key';
|
|
149
|
+
replacements['POLAR_ACCESS_TOKEN'] = answers.polarAccessToken || 'polar_oat_your_token';
|
|
150
|
+
replacements['POLAR_WEBHOOK_SECRET'] = answers.polarWebhookSecret || 'polar_whs_your_secret';
|
|
151
|
+
replacements['GOOGLE_CLIENT_ID'] = answers.googleClientId || 'YOUR_GOOGLE_CLIENT_ID';
|
|
152
|
+
replacements['GOOGLE_CLIENT_SECRET'] = answers.googleClientSecret || 'YOUR_GOOGLE_CLIENT_SECRET';
|
|
176
153
|
|
|
177
154
|
// Update environment variables
|
|
178
155
|
for (const [key, value] of Object.entries(replacements)) {
|