@launchframe/cli 1.0.0-beta.8 → 1.0.0
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/CODE_OF_CONDUCT.md +128 -0
- package/LICENSE +21 -0
- package/README.md +7 -1
- package/package.json +9 -6
- 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 +10 -3
- package/src/commands/deploy-init.js +24 -57
- package/src/commands/deploy-set-env.js +17 -7
- package/src/commands/deploy-sync-features.js +233 -0
- package/src/commands/deploy-up.js +4 -3
- package/src/commands/dev-add-user.js +165 -0
- package/src/commands/dev-logo.js +160 -0
- package/src/commands/dev-npm-install.js +33 -0
- package/src/commands/dev-queue.js +85 -0
- package/src/commands/docker-build.js +9 -6
- package/src/commands/help.js +35 -11
- package/src/commands/init.js +48 -56
- 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/module.js +146 -0
- package/src/commands/service.js +6 -6
- package/src/commands/waitlist-deploy.js +1 -0
- package/src/generator.js +43 -42
- package/src/index.js +109 -4
- package/src/services/module-config.js +25 -0
- package/src/services/module-registry.js +12 -0
- package/src/services/variant-config.js +24 -13
- 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/module-installer.js +58 -0
- package/src/utils/project-helpers.js +34 -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);
|
|
@@ -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
|
-
// Note: admin-portal folder might not exist yet in templates, skip if missing
|
|
75
|
+
// Process admin-portal
|
|
76
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
|
-
// Note: customers-portal folder might not exist yet in templates, skip if missing
|
|
101
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,31 @@ async function generateProject(answers, variantChoices, templateRoot) {
|
|
|
166
160
|
}
|
|
167
161
|
}
|
|
168
162
|
|
|
169
|
-
//
|
|
170
|
-
|
|
163
|
+
// Copy MCP configuration files from template root
|
|
164
|
+
logger.detail('Copying MCP configuration files...');
|
|
165
|
+
const mcpFiles = ['CLAUDE.md', '.mcp.json'];
|
|
166
|
+
for (const file of mcpFiles) {
|
|
167
|
+
const sourcePath = path.join(templateRoot, file);
|
|
168
|
+
const destPath = path.join(destinationRoot, file);
|
|
169
|
+
if (await fs.pathExists(sourcePath)) {
|
|
170
|
+
await fs.copy(sourcePath, destPath);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Generate .env file
|
|
175
|
+
console.log(chalk.gray(' Generating environment file...'));
|
|
171
176
|
const { envPath } = await generateEnvFile(destinationRoot, answers);
|
|
172
|
-
|
|
177
|
+
logger.detail(`Environment file: ${envPath}`);
|
|
173
178
|
|
|
174
|
-
//
|
|
175
|
-
|
|
179
|
+
// Create .launchframe marker file
|
|
180
|
+
logger.detail('Creating project marker file...');
|
|
176
181
|
const markerPath = path.join(destinationRoot, '.launchframe');
|
|
177
|
-
|
|
178
|
-
// Determine which services were installed
|
|
182
|
+
|
|
179
183
|
const installedServices = ['backend', 'admin-portal', 'infrastructure', 'website'];
|
|
180
184
|
if (variantChoices.userModel === 'b2b2c') {
|
|
181
185
|
installedServices.push('customers-portal');
|
|
182
186
|
}
|
|
183
|
-
|
|
187
|
+
|
|
184
188
|
const markerContent = {
|
|
185
189
|
version: '0.1.0',
|
|
186
190
|
createdAt: new Date().toISOString(),
|
|
@@ -188,12 +192,9 @@ async function generateProject(answers, variantChoices, templateRoot) {
|
|
|
188
192
|
projectDisplayName: answers.projectDisplayName,
|
|
189
193
|
deployConfigured: false,
|
|
190
194
|
installedServices: installedServices,
|
|
191
|
-
// Store variant choices for future reference
|
|
192
195
|
variants: variantChoices
|
|
193
196
|
};
|
|
194
197
|
await fs.writeJson(markerPath, markerContent, { spaces: 2 });
|
|
195
|
-
|
|
196
|
-
console.log('✅ Base project generated with variants applied');
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
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');
|
|
@@ -25,7 +41,13 @@ const {
|
|
|
25
41
|
serviceList,
|
|
26
42
|
serviceRemove
|
|
27
43
|
} = require('./commands/service');
|
|
44
|
+
const { moduleAdd, moduleList } = require('./commands/module');
|
|
28
45
|
const { cacheClear, cacheInfo, cacheUpdate } = require('./commands/cache');
|
|
46
|
+
const { devAddUser } = require('./commands/dev-add-user');
|
|
47
|
+
const { devQueue } = require('./commands/dev-queue');
|
|
48
|
+
const { devLogo } = require('./commands/dev-logo');
|
|
49
|
+
const { devNpmInstall } = require('./commands/dev-npm-install');
|
|
50
|
+
const { deploySyncFeatures } = require('./commands/deploy-sync-features');
|
|
29
51
|
|
|
30
52
|
// Get command and arguments
|
|
31
53
|
const command = process.argv[2];
|
|
@@ -62,9 +84,23 @@ function parseFlags(args) {
|
|
|
62
84
|
* Main CLI router
|
|
63
85
|
*/
|
|
64
86
|
async function main() {
|
|
87
|
+
initTelemetry();
|
|
88
|
+
|
|
65
89
|
const inProject = isLaunchFrameProject();
|
|
66
90
|
const flags = parseFlags(args);
|
|
67
91
|
|
|
92
|
+
// Handle version flag (only as standalone command)
|
|
93
|
+
if (command === '--version') {
|
|
94
|
+
const packageJson = require('../package.json');
|
|
95
|
+
console.log(packageJson.version);
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Set verbose mode globally
|
|
100
|
+
if (flags.verbose || flags.v) {
|
|
101
|
+
logger.setVerbose(true);
|
|
102
|
+
}
|
|
103
|
+
|
|
68
104
|
// No command provided
|
|
69
105
|
if (!command) {
|
|
70
106
|
help();
|
|
@@ -74,7 +110,7 @@ async function main() {
|
|
|
74
110
|
// Route commands
|
|
75
111
|
switch (command) {
|
|
76
112
|
case 'init':
|
|
77
|
-
await init({
|
|
113
|
+
await init({
|
|
78
114
|
projectName: flags['project-name'],
|
|
79
115
|
tenancy: flags['tenancy'],
|
|
80
116
|
userModel: flags['user-model']
|
|
@@ -92,6 +128,12 @@ async function main() {
|
|
|
92
128
|
case 'deploy:up':
|
|
93
129
|
await deployUp();
|
|
94
130
|
break;
|
|
131
|
+
case 'deploy:build':
|
|
132
|
+
await deployBuild(args[1]); // Optional service name
|
|
133
|
+
break;
|
|
134
|
+
case 'deploy:sync-features':
|
|
135
|
+
await deploySyncFeatures();
|
|
136
|
+
break;
|
|
95
137
|
case 'waitlist:deploy':
|
|
96
138
|
await waitlistDeploy();
|
|
97
139
|
break;
|
|
@@ -105,7 +147,7 @@ async function main() {
|
|
|
105
147
|
await waitlistLogs();
|
|
106
148
|
break;
|
|
107
149
|
case 'docker:build':
|
|
108
|
-
await dockerBuild();
|
|
150
|
+
await dockerBuild(args[1]); // Optional service name
|
|
109
151
|
break;
|
|
110
152
|
case 'docker:up':
|
|
111
153
|
await dockerUp(args[1]); // Pass optional service name
|
|
@@ -119,6 +161,18 @@ async function main() {
|
|
|
119
161
|
case 'docker:destroy':
|
|
120
162
|
await dockerDestroy({ force: flags.force || flags.f });
|
|
121
163
|
break;
|
|
164
|
+
case 'migration:run':
|
|
165
|
+
await migrateRun();
|
|
166
|
+
break;
|
|
167
|
+
case 'migration:create':
|
|
168
|
+
await migrateCreate();
|
|
169
|
+
break;
|
|
170
|
+
case 'migration:revert':
|
|
171
|
+
await migrateRevert();
|
|
172
|
+
break;
|
|
173
|
+
case 'database:console':
|
|
174
|
+
await databaseConsole({ remote: flags.remote });
|
|
175
|
+
break;
|
|
122
176
|
case 'doctor':
|
|
123
177
|
await doctor();
|
|
124
178
|
break;
|
|
@@ -141,6 +195,17 @@ async function main() {
|
|
|
141
195
|
}
|
|
142
196
|
await serviceRemove(args[1]);
|
|
143
197
|
break;
|
|
198
|
+
case 'module:add':
|
|
199
|
+
if (!args[1]) {
|
|
200
|
+
console.error(chalk.red('Error: Module name required'));
|
|
201
|
+
console.log('Usage: launchframe module:add <module-name>');
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
await moduleAdd(args[1]);
|
|
205
|
+
break;
|
|
206
|
+
case 'module:list':
|
|
207
|
+
await moduleList();
|
|
208
|
+
break;
|
|
144
209
|
case 'cache:clear':
|
|
145
210
|
await cacheClear();
|
|
146
211
|
break;
|
|
@@ -150,16 +215,56 @@ async function main() {
|
|
|
150
215
|
case 'cache:update':
|
|
151
216
|
await cacheUpdate();
|
|
152
217
|
break;
|
|
218
|
+
case 'dev:add-user':
|
|
219
|
+
await devAddUser();
|
|
220
|
+
break;
|
|
221
|
+
case 'dev:queue':
|
|
222
|
+
await devQueue();
|
|
223
|
+
break;
|
|
224
|
+
case 'dev:logo':
|
|
225
|
+
await devLogo();
|
|
226
|
+
break;
|
|
227
|
+
case 'dev:npm-install':
|
|
228
|
+
if (!args[1]) {
|
|
229
|
+
console.error(chalk.red('Error: Service name required'));
|
|
230
|
+
console.log('Usage: launchframe dev:npm-install <service> [packages...]');
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
await devNpmInstall(args[1], args.slice(2));
|
|
234
|
+
break;
|
|
235
|
+
case 'telemetry':
|
|
236
|
+
if (flags.disable) {
|
|
237
|
+
setTelemetryEnabled(false);
|
|
238
|
+
} else if (flags.enable) {
|
|
239
|
+
setTelemetryEnabled(true);
|
|
240
|
+
} else {
|
|
241
|
+
showTelemetryStatus();
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
153
244
|
case 'help':
|
|
154
245
|
case '--help':
|
|
155
246
|
case '-h':
|
|
156
247
|
help();
|
|
157
248
|
break;
|
|
158
249
|
default:
|
|
159
|
-
console.error(chalk.red(`\
|
|
250
|
+
console.error(chalk.red(`\nUnknown command: ${command}\n`));
|
|
160
251
|
help();
|
|
161
252
|
process.exit(1);
|
|
162
253
|
}
|
|
163
254
|
}
|
|
164
255
|
|
|
165
|
-
main()
|
|
256
|
+
main()
|
|
257
|
+
.then(() => {
|
|
258
|
+
if (command && command !== 'help' && command !== '--help' && command !== '-h' && command !== '--version') {
|
|
259
|
+
trackEvent('command_executed', { command, success: true });
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
.catch((error) => {
|
|
263
|
+
trackEvent('command_executed', {
|
|
264
|
+
command,
|
|
265
|
+
success: false,
|
|
266
|
+
error_message: sanitize(error.message)
|
|
267
|
+
});
|
|
268
|
+
console.error(chalk.red(error.message));
|
|
269
|
+
process.exit(1);
|
|
270
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Module configuration - defines files, sections, and dependencies for each module
|
|
2
|
+
const MODULE_CONFIG = {
|
|
3
|
+
blog: {
|
|
4
|
+
website: {
|
|
5
|
+
files: [
|
|
6
|
+
'src/lib/blog.ts',
|
|
7
|
+
'src/types/blog.ts',
|
|
8
|
+
'src/app/blog',
|
|
9
|
+
'src/components/blog',
|
|
10
|
+
'src/app/sitemap.ts',
|
|
11
|
+
'content/blog',
|
|
12
|
+
],
|
|
13
|
+
sections: {
|
|
14
|
+
'src/components/layout/Navbar.tsx': ['BLOG_NAV_LINK'],
|
|
15
|
+
'src/components/layout/Footer.tsx': ['BLOG_FOOTER_LINK'],
|
|
16
|
+
},
|
|
17
|
+
dependencies: {
|
|
18
|
+
'gray-matter': '^4.0.3',
|
|
19
|
+
'marked': '^12.0.0',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
module.exports = { MODULE_CONFIG };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Module registry - available modules for LaunchFrame services
|
|
2
|
+
const MODULE_REGISTRY = {
|
|
3
|
+
blog: {
|
|
4
|
+
name: 'blog',
|
|
5
|
+
displayName: 'Blog',
|
|
6
|
+
description: 'Markdown-based blog using local .md files with YAML front-matter — no database required',
|
|
7
|
+
services: ['website'],
|
|
8
|
+
version: '1.0.0'
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
module.exports = { MODULE_REGISTRY };
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -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;
|