@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.
- package/.claude/settings.local.json +12 -0
- package/CLAUDE.md +27 -0
- package/LICENSE +21 -0
- package/README.md +7 -1
- package/package.json +4 -3
- package/src/commands/cache.js +14 -14
- package/src/commands/database-console.js +84 -0
- package/src/commands/deploy-build.js +76 -0
- package/src/commands/deploy-configure.js +15 -6
- package/src/commands/deploy-init.js +24 -57
- package/src/commands/deploy-set-env.js +17 -7
- package/src/commands/deploy-up.js +4 -3
- package/src/commands/dev-add-user.js +165 -0
- package/src/commands/dev-queue.js +85 -0
- package/src/commands/help.js +29 -11
- package/src/commands/init.js +49 -64
- package/src/commands/migration-create.js +40 -0
- package/src/commands/migration-revert.js +32 -0
- package/src/commands/migration-run.js +32 -0
- package/src/commands/service.js +12 -6
- package/src/commands/waitlist-deploy.js +1 -0
- package/src/generator.js +34 -44
- package/src/index.js +81 -12
- package/src/services/variant-config.js +36 -25
- package/src/utils/docker-helper.js +116 -2
- package/src/utils/env-generator.js +9 -6
- package/src/utils/env-validator.js +4 -2
- package/src/utils/github-access.js +19 -17
- package/src/utils/logger.js +93 -0
- package/src/utils/project-helpers.js +5 -1
- package/src/utils/{module-cache.js → service-cache.js} +67 -73
- package/src/utils/ssh-helper.js +51 -1
- package/src/utils/telemetry.js +238 -0
- package/src/utils/variable-replacer.js +18 -23
- package/src/utils/variant-processor.js +35 -42
package/src/commands/service.js
CHANGED
|
@@ -8,7 +8,7 @@ const { isLaunchFrameProject, getProjectConfig, updateProjectConfig, getPrimaryD
|
|
|
8
8
|
const { replaceVariables } = require('../utils/variable-replacer');
|
|
9
9
|
const { updateEnvFile } = require('../utils/env-generator');
|
|
10
10
|
const { checkGitHubAccess, showAccessDeniedMessage } = require('../utils/github-access');
|
|
11
|
-
const { ensureCacheReady,
|
|
11
|
+
const { ensureCacheReady, getServicePath } = require('../utils/service-cache');
|
|
12
12
|
|
|
13
13
|
async function serviceAdd(serviceName) {
|
|
14
14
|
// STEP 1: Validation
|
|
@@ -75,13 +75,13 @@ async function serviceAdd(serviceName) {
|
|
|
75
75
|
let sourceDir;
|
|
76
76
|
|
|
77
77
|
if (isDevMode) {
|
|
78
|
-
// Local development: copy from launchframe-dev/
|
|
78
|
+
// Local development: copy from launchframe-dev/services directory
|
|
79
79
|
console.log(chalk.blue('\n[DEV MODE] Copying service from local directory...'));
|
|
80
|
-
sourceDir = path.resolve(__dirname, '../../../
|
|
80
|
+
sourceDir = path.resolve(__dirname, '../../../services', serviceName);
|
|
81
81
|
|
|
82
82
|
if (!fs.existsSync(sourceDir)) {
|
|
83
83
|
console.error(chalk.red(`Error: Local service directory not found: ${sourceDir}`));
|
|
84
|
-
console.log('Make sure the service exists in the
|
|
84
|
+
console.log('Make sure the service exists in the services directory');
|
|
85
85
|
process.exit(1);
|
|
86
86
|
}
|
|
87
87
|
} else {
|
|
@@ -98,9 +98,9 @@ async function serviceAdd(serviceName) {
|
|
|
98
98
|
console.log(chalk.green('✓ Repository access confirmed'));
|
|
99
99
|
|
|
100
100
|
try {
|
|
101
|
-
// Ensure cache has this service
|
|
101
|
+
// Ensure cache has this service
|
|
102
102
|
await ensureCacheReady([serviceName]);
|
|
103
|
-
sourceDir =
|
|
103
|
+
sourceDir = getServicePath(serviceName);
|
|
104
104
|
} catch (error) {
|
|
105
105
|
console.error(chalk.red(`\n❌ Error: ${error.message}\n`));
|
|
106
106
|
process.exit(1);
|
|
@@ -464,6 +464,12 @@ function getDockerComposeDefinitions(serviceName, service, projectName, projectC
|
|
|
464
464
|
${serviceName}:
|
|
465
465
|
image: ghcr.io/${githubOrg}/${projectName}-${serviceName}:latest
|
|
466
466
|
restart: unless-stopped
|
|
467
|
+
healthcheck:
|
|
468
|
+
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/"]
|
|
469
|
+
interval: 30s
|
|
470
|
+
timeout: 10s
|
|
471
|
+
start_period: 40s
|
|
472
|
+
retries: 3
|
|
467
473
|
labels:
|
|
468
474
|
- "traefik.enable=true"
|
|
469
475
|
- "traefik.http.routers.${serviceName}.rule=Host(\`${serviceName}.\${PRIMARY_DOMAIN}\`)"
|
|
@@ -205,6 +205,7 @@ async function waitlistDeploy() {
|
|
|
205
205
|
verifySpinner.succeed('Services verified');
|
|
206
206
|
console.log(chalk.gray('\n' + psOutput));
|
|
207
207
|
} catch (error) {
|
|
208
|
+
console.error(chalk.yellow(`\n⚠️ Error: ${error.message}\n`));
|
|
208
209
|
verifySpinner.warn('Could not verify services');
|
|
209
210
|
}
|
|
210
211
|
|
package/src/generator.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
3
|
const { execSync } = require('child_process');
|
|
4
|
+
const chalk = require('chalk');
|
|
4
5
|
const { replaceVariables } = require('./utils/variable-replacer');
|
|
5
6
|
const { copyDirectory } = require('./utils/file-ops');
|
|
6
7
|
const { generateEnvFile } = require('./utils/env-generator');
|
|
7
8
|
const { processServiceVariant } = require('./utils/variant-processor');
|
|
8
9
|
const { resolveVariantChoices } = require('./services/variant-config');
|
|
10
|
+
const logger = require('./utils/logger');
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Initialize git repository in a service directory
|
|
@@ -14,13 +16,13 @@ const { resolveVariantChoices } = require('./services/variant-config');
|
|
|
14
16
|
*/
|
|
15
17
|
function initGitRepo(servicePath, serviceName) {
|
|
16
18
|
try {
|
|
17
|
-
|
|
19
|
+
logger.detail(`Initializing git repository for ${serviceName}`);
|
|
18
20
|
execSync('git init', { cwd: servicePath, stdio: 'ignore' });
|
|
19
21
|
execSync('git add .', { cwd: servicePath, stdio: 'ignore' });
|
|
20
22
|
execSync('git commit -m "Initial commit"', { cwd: servicePath, stdio: 'ignore' });
|
|
21
|
-
|
|
23
|
+
logger.detail(`Git initialized: ${serviceName}`);
|
|
22
24
|
} catch (error) {
|
|
23
|
-
|
|
25
|
+
logger.warn(`Could not initialize git for ${serviceName}: ${error.message}`);
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -34,12 +36,11 @@ async function generateProject(answers, variantChoices, templateRoot) {
|
|
|
34
36
|
const { projectName } = answers;
|
|
35
37
|
|
|
36
38
|
// Define source (template) and destination paths
|
|
37
|
-
|
|
38
|
-
const projectRoot = path.resolve(__dirname, '../..'); // For root-level files like .github, README.md
|
|
39
|
+
const projectRoot = path.resolve(__dirname, '../..'); // For root-level files
|
|
39
40
|
const destinationRoot = path.resolve(process.cwd(), projectName);
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
logger.detail(`Template source: ${templateRoot}`);
|
|
43
|
+
logger.detail(`Destination: ${destinationRoot}`);
|
|
43
44
|
|
|
44
45
|
// Ensure destination directory exists
|
|
45
46
|
await fs.ensureDir(destinationRoot);
|
|
@@ -60,8 +61,8 @@ async function generateProject(answers, variantChoices, templateRoot) {
|
|
|
60
61
|
// Resolve variant choices for all services
|
|
61
62
|
const allServiceVariants = resolveVariantChoices(variantChoices);
|
|
62
63
|
|
|
63
|
-
//
|
|
64
|
-
console.log('
|
|
64
|
+
// Process backend
|
|
65
|
+
console.log(chalk.gray(' Processing backend...'));
|
|
65
66
|
await processServiceVariant(
|
|
66
67
|
'backend',
|
|
67
68
|
allServiceVariants.backend,
|
|
@@ -71,11 +72,10 @@ async function generateProject(answers, variantChoices, templateRoot) {
|
|
|
71
72
|
);
|
|
72
73
|
initGitRepo(path.join(destinationRoot, 'backend'), 'backend');
|
|
73
74
|
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
const adminPortalTemplatePath = path.join(templateRoot, 'admin-portal/templates/base');
|
|
75
|
+
// Process admin-portal
|
|
76
|
+
const adminPortalTemplatePath = path.join(templateRoot, 'admin-portal/base');
|
|
77
77
|
if (await fs.pathExists(adminPortalTemplatePath)) {
|
|
78
|
-
console.log('
|
|
78
|
+
console.log(chalk.gray(' Processing admin-portal...'));
|
|
79
79
|
await processServiceVariant(
|
|
80
80
|
'admin-portal',
|
|
81
81
|
allServiceVariants['admin-portal'],
|
|
@@ -85,8 +85,8 @@ async function generateProject(answers, variantChoices, templateRoot) {
|
|
|
85
85
|
);
|
|
86
86
|
initGitRepo(path.join(destinationRoot, 'admin-portal'), 'admin-portal');
|
|
87
87
|
} else {
|
|
88
|
-
// Fallback: Copy admin-portal directly without variants
|
|
89
|
-
console.log('
|
|
88
|
+
// Fallback: Copy admin-portal directly without variants
|
|
89
|
+
console.log(chalk.gray(' Copying admin-portal...'));
|
|
90
90
|
const adminPortalSource = path.join(templateRoot, 'admin-portal');
|
|
91
91
|
if (await fs.pathExists(adminPortalSource)) {
|
|
92
92
|
await copyDirectory(adminPortalSource, path.join(destinationRoot, 'admin-portal'));
|
|
@@ -95,12 +95,11 @@ async function generateProject(answers, variantChoices, templateRoot) {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
//
|
|
98
|
+
// Process customers-portal (only if B2B2C)
|
|
99
99
|
if (variantChoices.userModel === 'b2b2c') {
|
|
100
|
-
|
|
101
|
-
const customersPortalTemplatePath = path.join(templateRoot, 'customers-portal/templates/base');
|
|
100
|
+
const customersPortalTemplatePath = path.join(templateRoot, 'customers-portal/base');
|
|
102
101
|
if (await fs.pathExists(customersPortalTemplatePath)) {
|
|
103
|
-
console.log('
|
|
102
|
+
console.log(chalk.gray(' Processing customers-portal...'));
|
|
104
103
|
await processServiceVariant(
|
|
105
104
|
'customers-portal',
|
|
106
105
|
allServiceVariants['customers-portal'],
|
|
@@ -110,8 +109,7 @@ async function generateProject(answers, variantChoices, templateRoot) {
|
|
|
110
109
|
);
|
|
111
110
|
initGitRepo(path.join(destinationRoot, 'customers-portal'), 'customers-portal');
|
|
112
111
|
} else {
|
|
113
|
-
|
|
114
|
-
console.log('📋 Copying customers-portal service (B2B2C mode)...');
|
|
112
|
+
console.log(chalk.gray(' Copying customers-portal...'));
|
|
115
113
|
const customersPortalSource = path.join(templateRoot, 'customers-portal');
|
|
116
114
|
if (await fs.pathExists(customersPortalSource)) {
|
|
117
115
|
await copyDirectory(customersPortalSource, path.join(destinationRoot, 'customers-portal'));
|
|
@@ -120,11 +118,11 @@ async function generateProject(answers, variantChoices, templateRoot) {
|
|
|
120
118
|
}
|
|
121
119
|
}
|
|
122
120
|
} else {
|
|
123
|
-
|
|
121
|
+
logger.detail('Skipping customers-portal (B2B mode)');
|
|
124
122
|
}
|
|
125
123
|
|
|
126
|
-
//
|
|
127
|
-
console.log('
|
|
124
|
+
// Process infrastructure
|
|
125
|
+
console.log(chalk.gray(' Processing infrastructure...'));
|
|
128
126
|
await processServiceVariant(
|
|
129
127
|
'infrastructure',
|
|
130
128
|
allServiceVariants.infrastructure,
|
|
@@ -134,7 +132,8 @@ async function generateProject(answers, variantChoices, templateRoot) {
|
|
|
134
132
|
);
|
|
135
133
|
initGitRepo(path.join(destinationRoot, 'infrastructure'), 'infrastructure');
|
|
136
134
|
|
|
137
|
-
|
|
135
|
+
// Process website
|
|
136
|
+
console.log(chalk.gray(' Processing website...'));
|
|
138
137
|
await copyDirectory(
|
|
139
138
|
path.join(templateRoot, 'website'),
|
|
140
139
|
path.join(destinationRoot, 'website')
|
|
@@ -142,14 +141,9 @@ async function generateProject(answers, variantChoices, templateRoot) {
|
|
|
142
141
|
await replaceVariables(path.join(destinationRoot, 'website'), variables);
|
|
143
142
|
initGitRepo(path.join(destinationRoot, 'website'), 'website');
|
|
144
143
|
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
const additionalFiles = [
|
|
148
|
-
'.github',
|
|
149
|
-
'README.md',
|
|
150
|
-
'.gitignore',
|
|
151
|
-
'LICENSE'
|
|
152
|
-
];
|
|
144
|
+
// Copy additional files
|
|
145
|
+
logger.detail('Copying additional files...');
|
|
146
|
+
const additionalFiles = ['.github', 'README.md', '.gitignore', 'LICENSE'];
|
|
153
147
|
|
|
154
148
|
for (const file of additionalFiles) {
|
|
155
149
|
const sourcePath = path.join(projectRoot, file);
|
|
@@ -166,21 +160,20 @@ async function generateProject(answers, variantChoices, templateRoot) {
|
|
|
166
160
|
}
|
|
167
161
|
}
|
|
168
162
|
|
|
169
|
-
//
|
|
170
|
-
console.log('
|
|
163
|
+
// Generate .env file
|
|
164
|
+
console.log(chalk.gray(' Generating environment file...'));
|
|
171
165
|
const { envPath } = await generateEnvFile(destinationRoot, answers);
|
|
172
|
-
|
|
166
|
+
logger.detail(`Environment file: ${envPath}`);
|
|
173
167
|
|
|
174
|
-
//
|
|
175
|
-
|
|
168
|
+
// Create .launchframe marker file
|
|
169
|
+
logger.detail('Creating project marker file...');
|
|
176
170
|
const markerPath = path.join(destinationRoot, '.launchframe');
|
|
177
|
-
|
|
178
|
-
// Determine which services were installed
|
|
171
|
+
|
|
179
172
|
const installedServices = ['backend', 'admin-portal', 'infrastructure', 'website'];
|
|
180
173
|
if (variantChoices.userModel === 'b2b2c') {
|
|
181
174
|
installedServices.push('customers-portal');
|
|
182
175
|
}
|
|
183
|
-
|
|
176
|
+
|
|
184
177
|
const markerContent = {
|
|
185
178
|
version: '0.1.0',
|
|
186
179
|
createdAt: new Date().toISOString(),
|
|
@@ -188,12 +181,9 @@ async function generateProject(answers, variantChoices, templateRoot) {
|
|
|
188
181
|
projectDisplayName: answers.projectDisplayName,
|
|
189
182
|
deployConfigured: false,
|
|
190
183
|
installedServices: installedServices,
|
|
191
|
-
// Store variant choices for future reference
|
|
192
184
|
variants: variantChoices
|
|
193
185
|
};
|
|
194
186
|
await fs.writeJson(markerPath, markerContent, { spaces: 2 });
|
|
195
|
-
|
|
196
|
-
console.log('✅ Base project generated with variants applied');
|
|
197
187
|
}
|
|
198
188
|
|
|
199
189
|
module.exports = { generateProject };
|
package/src/index.js
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const { isLaunchFrameProject } = require('./utils/project-helpers');
|
|
5
|
+
const logger = require('./utils/logger');
|
|
6
|
+
const { initTelemetry, trackEvent, sanitize, setTelemetryEnabled, showTelemetryStatus } = require('./utils/telemetry');
|
|
7
|
+
|
|
8
|
+
// Detect locally linked version: npm link installs to global node_modules
|
|
9
|
+
// as a symlink. When running from a real install, __dirname is inside the
|
|
10
|
+
// global node_modules folder. When linked, it resolves to the source directory.
|
|
11
|
+
const isDevMode = !__dirname.includes('node_modules');
|
|
12
|
+
if (isDevMode) {
|
|
13
|
+
const packageJson = require('../package.json');
|
|
14
|
+
console.log(chalk.yellow(`⚠ Running locally linked CLI v${packageJson.version} (${__dirname})`));
|
|
15
|
+
}
|
|
5
16
|
|
|
6
17
|
// Import commands
|
|
7
18
|
const { init } = require('./commands/init');
|
|
@@ -9,6 +20,7 @@ const { deployConfigure } = require('./commands/deploy-configure');
|
|
|
9
20
|
const { deploySetEnv } = require('./commands/deploy-set-env');
|
|
10
21
|
const { deployInit } = require('./commands/deploy-init');
|
|
11
22
|
const { deployUp } = require('./commands/deploy-up');
|
|
23
|
+
const { deployBuild } = require('./commands/deploy-build');
|
|
12
24
|
const { waitlistDeploy } = require('./commands/waitlist-deploy');
|
|
13
25
|
const { waitlistUp } = require('./commands/waitlist-up');
|
|
14
26
|
const { waitlistDown } = require('./commands/waitlist-down');
|
|
@@ -17,6 +29,10 @@ const { dockerBuild } = require('./commands/docker-build');
|
|
|
17
29
|
const { dockerUp } = require('./commands/docker-up');
|
|
18
30
|
const { dockerDown } = require('./commands/docker-down');
|
|
19
31
|
const { dockerLogs } = require('./commands/docker-logs');
|
|
32
|
+
const { migrateRun } = require('./commands/migration-run');
|
|
33
|
+
const { migrateCreate } = require('./commands/migration-create');
|
|
34
|
+
const { migrateRevert } = require('./commands/migration-revert');
|
|
35
|
+
const { databaseConsole } = require('./commands/database-console');
|
|
20
36
|
const { dockerDestroy } = require('./commands/docker-destroy');
|
|
21
37
|
const { doctor } = require('./commands/doctor');
|
|
22
38
|
const { help } = require('./commands/help');
|
|
@@ -26,6 +42,8 @@ const {
|
|
|
26
42
|
serviceRemove
|
|
27
43
|
} = require('./commands/service');
|
|
28
44
|
const { cacheClear, cacheInfo, cacheUpdate } = require('./commands/cache');
|
|
45
|
+
const { devAddUser } = require('./commands/dev-add-user');
|
|
46
|
+
const { devQueue } = require('./commands/dev-queue');
|
|
29
47
|
|
|
30
48
|
// Get command and arguments
|
|
31
49
|
const command = process.argv[2];
|
|
@@ -62,26 +80,33 @@ function parseFlags(args) {
|
|
|
62
80
|
* Main CLI router
|
|
63
81
|
*/
|
|
64
82
|
async function main() {
|
|
83
|
+
initTelemetry();
|
|
84
|
+
|
|
65
85
|
const inProject = isLaunchFrameProject();
|
|
66
86
|
const flags = parseFlags(args);
|
|
67
87
|
|
|
88
|
+
// Handle version flag (only as standalone command)
|
|
89
|
+
if (command === '--version') {
|
|
90
|
+
const packageJson = require('../package.json');
|
|
91
|
+
console.log(packageJson.version);
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Set verbose mode globally
|
|
96
|
+
if (flags.verbose || flags.v) {
|
|
97
|
+
logger.setVerbose(true);
|
|
98
|
+
}
|
|
99
|
+
|
|
68
100
|
// No command provided
|
|
69
101
|
if (!command) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
help();
|
|
73
|
-
process.exit(1);
|
|
74
|
-
} else {
|
|
75
|
-
// Outside project, default to init
|
|
76
|
-
await init(flags);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
102
|
+
help();
|
|
103
|
+
process.exit(inProject ? 1 : 0);
|
|
79
104
|
}
|
|
80
105
|
|
|
81
106
|
// Route commands
|
|
82
107
|
switch (command) {
|
|
83
108
|
case 'init':
|
|
84
|
-
await init({
|
|
109
|
+
await init({
|
|
85
110
|
projectName: flags['project-name'],
|
|
86
111
|
tenancy: flags['tenancy'],
|
|
87
112
|
userModel: flags['user-model']
|
|
@@ -99,6 +124,9 @@ async function main() {
|
|
|
99
124
|
case 'deploy:up':
|
|
100
125
|
await deployUp();
|
|
101
126
|
break;
|
|
127
|
+
case 'deploy:build':
|
|
128
|
+
await deployBuild(args[1]); // Optional service name
|
|
129
|
+
break;
|
|
102
130
|
case 'waitlist:deploy':
|
|
103
131
|
await waitlistDeploy();
|
|
104
132
|
break;
|
|
@@ -126,6 +154,18 @@ async function main() {
|
|
|
126
154
|
case 'docker:destroy':
|
|
127
155
|
await dockerDestroy({ force: flags.force || flags.f });
|
|
128
156
|
break;
|
|
157
|
+
case 'migration:run':
|
|
158
|
+
await migrateRun();
|
|
159
|
+
break;
|
|
160
|
+
case 'migration:create':
|
|
161
|
+
await migrateCreate();
|
|
162
|
+
break;
|
|
163
|
+
case 'migration:revert':
|
|
164
|
+
await migrateRevert();
|
|
165
|
+
break;
|
|
166
|
+
case 'database:console':
|
|
167
|
+
await databaseConsole({ remote: flags.remote });
|
|
168
|
+
break;
|
|
129
169
|
case 'doctor':
|
|
130
170
|
await doctor();
|
|
131
171
|
break;
|
|
@@ -157,16 +197,45 @@ async function main() {
|
|
|
157
197
|
case 'cache:update':
|
|
158
198
|
await cacheUpdate();
|
|
159
199
|
break;
|
|
200
|
+
case 'dev:add-user':
|
|
201
|
+
await devAddUser();
|
|
202
|
+
break;
|
|
203
|
+
case 'dev:queue':
|
|
204
|
+
await devQueue();
|
|
205
|
+
break;
|
|
206
|
+
case 'telemetry':
|
|
207
|
+
if (flags.disable) {
|
|
208
|
+
setTelemetryEnabled(false);
|
|
209
|
+
} else if (flags.enable) {
|
|
210
|
+
setTelemetryEnabled(true);
|
|
211
|
+
} else {
|
|
212
|
+
showTelemetryStatus();
|
|
213
|
+
}
|
|
214
|
+
break;
|
|
160
215
|
case 'help':
|
|
161
216
|
case '--help':
|
|
162
217
|
case '-h':
|
|
163
218
|
help();
|
|
164
219
|
break;
|
|
165
220
|
default:
|
|
166
|
-
console.error(chalk.red(`\
|
|
221
|
+
console.error(chalk.red(`\nUnknown command: ${command}\n`));
|
|
167
222
|
help();
|
|
168
223
|
process.exit(1);
|
|
169
224
|
}
|
|
170
225
|
}
|
|
171
226
|
|
|
172
|
-
main()
|
|
227
|
+
main()
|
|
228
|
+
.then(() => {
|
|
229
|
+
if (command && command !== 'help' && command !== '--help' && command !== '-h' && command !== '--version') {
|
|
230
|
+
trackEvent('command_executed', { command, success: true });
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
.catch((error) => {
|
|
234
|
+
trackEvent('command_executed', {
|
|
235
|
+
command,
|
|
236
|
+
success: false,
|
|
237
|
+
error_message: sanitize(error.message)
|
|
238
|
+
});
|
|
239
|
+
console.error(chalk.red(error.message));
|
|
240
|
+
process.exit(1);
|
|
241
|
+
});
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
|
|
12
12
|
const VARIANT_CONFIG = {
|
|
13
13
|
backend: {
|
|
14
|
-
base: 'backend/
|
|
15
|
-
sectionsDir: 'backend/
|
|
16
|
-
filesDir: 'backend/
|
|
14
|
+
base: 'backend/base',
|
|
15
|
+
sectionsDir: 'backend/variants/sections',
|
|
16
|
+
filesDir: 'backend/variants/files',
|
|
17
17
|
|
|
18
18
|
variants: {
|
|
19
19
|
// Multi-tenant variant: Adds project/workspace support
|
|
@@ -21,24 +21,16 @@ 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
|
-
'src/modules/auth/auth.service.ts', // Auth service with multi-tenant support
|
|
28
|
-
'src/modules/auth/auth.controller.ts', // Auth controller with multi-tenant support
|
|
29
26
|
'src/modules/users/users.service.ts', // Users service with multi-tenant support
|
|
30
27
|
'src/modules/users/users.controller.ts', // Users controller with multi-tenant support
|
|
31
28
|
'src/modules/users/create-user.dto.ts' // CreateUserDto with businessId
|
|
32
29
|
],
|
|
33
30
|
|
|
34
31
|
// Code sections to insert into base template files
|
|
32
|
+
// Note: main.ts uses PRIMARY_DOMAIN env var for dynamic CORS - no sections needed
|
|
35
33
|
sections: {
|
|
36
|
-
'src/main.ts': [
|
|
37
|
-
'PROJECT_IMPORTS', // Add project-related imports
|
|
38
|
-
'PROJECT_CUSTOM_DOMAINS', // Add custom domains query
|
|
39
|
-
'PROJECT_CUSTOM_DOMAINS_CORS', // Add custom domains to CORS
|
|
40
|
-
'PROJECT_GUARD' // Add ProjectOwnershipGuard registration
|
|
41
|
-
],
|
|
42
34
|
'src/modules/app/app.module.ts': [
|
|
43
35
|
'PROJECTS_MODULE_IMPORT', // Add ProjectsModule import
|
|
44
36
|
'PROJECTS_MODULE' // Add ProjectsModule to imports array
|
|
@@ -60,11 +52,15 @@ const VARIANT_CONFIG = {
|
|
|
60
52
|
}
|
|
61
53
|
},
|
|
62
54
|
|
|
63
|
-
// B2B2C variant: Adds regular_user support
|
|
55
|
+
// B2B2C variant: Adds regular_user support with separate customer auth
|
|
64
56
|
'b2b2c': {
|
|
65
57
|
// Complete files to copy
|
|
66
58
|
files: [
|
|
67
|
-
'src/modules/users/user-business.entity.ts',
|
|
59
|
+
'src/modules/users/user-business.entity.ts', // Business-to-user linking entity
|
|
60
|
+
'src/modules/auth/auth-customer.ts', // Customer auth config (regular_user, customer_ cookie)
|
|
61
|
+
'src/modules/auth/better-auth-customer.controller.ts', // Customer auth controller (/api/auth/customer)
|
|
62
|
+
'src/modules/auth/auth.module.ts', // Auth module with customer controller
|
|
63
|
+
'src/modules/auth/better-auth.guard.ts', // Guard handling both auth instances
|
|
68
64
|
],
|
|
69
65
|
|
|
70
66
|
// Code sections to insert
|
|
@@ -77,6 +73,9 @@ const VARIANT_CONFIG = {
|
|
|
77
73
|
'src/modules/users/users.module.ts': [
|
|
78
74
|
'B2B2C_IMPORTS', // Add UserBusiness import
|
|
79
75
|
'B2B2C_ENTITIES' // Add UserBusiness to TypeORM
|
|
76
|
+
],
|
|
77
|
+
'src/database/migrations/1764300000001-CreateSessionsTable.ts': [
|
|
78
|
+
'B2B2C_TENANT_COLUMN' // Add tenant_id column for session scoping
|
|
80
79
|
]
|
|
81
80
|
}
|
|
82
81
|
},
|
|
@@ -126,8 +125,7 @@ const VARIANT_CONFIG = {
|
|
|
126
125
|
// Complete files to copy (has both multi-tenant and B2B2C features)
|
|
127
126
|
files: [
|
|
128
127
|
'src/modules/users/user-business.entity.ts', // Business-to-user linking entity
|
|
129
|
-
'src/modules/auth/auth.
|
|
130
|
-
'src/modules/auth/auth.controller.ts', // Combined auth controller
|
|
128
|
+
'src/modules/auth/auth.ts', // Combined Better Auth config
|
|
131
129
|
'src/modules/users/users.service.ts', // Combined users service
|
|
132
130
|
'src/modules/users/users.controller.ts', // Combined users controller
|
|
133
131
|
'src/modules/domain/projects/projects.module.ts' // Projects module with UserBusiness
|
|
@@ -180,9 +178,9 @@ const VARIANT_CONFIG = {
|
|
|
180
178
|
|
|
181
179
|
// Admin portal inherits tenancy choice from backend
|
|
182
180
|
'admin-portal': {
|
|
183
|
-
base: 'admin-portal/
|
|
184
|
-
sectionsDir: 'admin-portal/
|
|
185
|
-
filesDir: 'admin-portal/
|
|
181
|
+
base: 'admin-portal/base',
|
|
182
|
+
sectionsDir: 'admin-portal/variants/sections',
|
|
183
|
+
filesDir: 'admin-portal/variants/files',
|
|
186
184
|
|
|
187
185
|
variants: {
|
|
188
186
|
'multi-tenant': {
|
|
@@ -284,9 +282,9 @@ const VARIANT_CONFIG = {
|
|
|
284
282
|
|
|
285
283
|
// Customers portal (B2B2C only - no pure B2B use case)
|
|
286
284
|
'customers-portal': {
|
|
287
|
-
base: 'customers-portal/
|
|
288
|
-
sectionsDir: 'customers-portal/
|
|
289
|
-
filesDir: 'customers-portal/
|
|
285
|
+
base: 'customers-portal/base', // B2B2C + Single-tenant base
|
|
286
|
+
sectionsDir: 'customers-portal/variants/sections',
|
|
287
|
+
filesDir: 'customers-portal/variants/files',
|
|
290
288
|
|
|
291
289
|
variants: {
|
|
292
290
|
'single-tenant': {
|
|
@@ -310,6 +308,14 @@ const VARIANT_CONFIG = {
|
|
|
310
308
|
'src/store/useProjectStore.ts' // Project state
|
|
311
309
|
],
|
|
312
310
|
sections: {}
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
'b2b2c': {
|
|
314
|
+
// B2B2C uses separate auth endpoint for customer sessions
|
|
315
|
+
files: [
|
|
316
|
+
'src/lib/auth-client.ts' // Auth client with /api/auth/customer basePath
|
|
317
|
+
],
|
|
318
|
+
sections: {}
|
|
313
319
|
}
|
|
314
320
|
},
|
|
315
321
|
|
|
@@ -321,9 +327,9 @@ const VARIANT_CONFIG = {
|
|
|
321
327
|
|
|
322
328
|
// Infrastructure (Docker Compose orchestration)
|
|
323
329
|
infrastructure: {
|
|
324
|
-
base: 'infrastructure',
|
|
325
|
-
sectionsDir: 'infrastructure/
|
|
326
|
-
filesDir: 'infrastructure/
|
|
330
|
+
base: 'infrastructure/base',
|
|
331
|
+
sectionsDir: 'infrastructure/variants/sections',
|
|
332
|
+
filesDir: 'infrastructure/variants/files',
|
|
327
333
|
|
|
328
334
|
variants: {
|
|
329
335
|
// B2B2C variant: Adds customers-portal service to docker-compose files
|
|
@@ -395,6 +401,11 @@ function resolveVariantChoices(backendChoices) {
|
|
|
395
401
|
choices['admin-portal'].userModel = backendChoices.userModel;
|
|
396
402
|
}
|
|
397
403
|
|
|
404
|
+
// Special case: customers-portal inherits BOTH tenancy and userModel
|
|
405
|
+
if (choices['customers-portal']) {
|
|
406
|
+
choices['customers-portal'].userModel = backendChoices.userModel;
|
|
407
|
+
}
|
|
408
|
+
|
|
398
409
|
// Special case: infrastructure needs BOTH tenancy and userModel for proper variant resolution
|
|
399
410
|
if (choices['infrastructure']) {
|
|
400
411
|
choices['infrastructure'].tenancy = backendChoices.tenancy;
|