@launchframe/cli 1.0.0-beta.2 โ†’ 1.0.0-beta.21

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.
@@ -209,6 +209,54 @@ function showDeployKeyInstructions(vpsUser, vpsHost, githubOrg, projectName) {
209
209
  console.log(chalk.gray(' launchframe deploy:init\n'));
210
210
  }
211
211
 
212
+ /**
213
+ * Pull Docker images on VPS
214
+ * @param {string} vpsUser - SSH username
215
+ * @param {string} vpsHost - VPS hostname or IP
216
+ * @param {string} vpsAppFolder - App folder path on VPS
217
+ * @returns {Promise<void>}
218
+ */
219
+ async function pullImagesOnVPS(vpsUser, vpsHost, vpsAppFolder) {
220
+ const ora = require('ora');
221
+
222
+ const spinner = ora('Pulling images on VPS...').start();
223
+
224
+ try {
225
+ await execAsync(
226
+ `ssh ${vpsUser}@${vpsHost} "cd ${vpsAppFolder}/infrastructure && docker-compose -f docker-compose.yml -f docker-compose.prod.yml pull"`,
227
+ { timeout: 600000 } // 10 minutes
228
+ );
229
+ spinner.succeed('Images pulled on VPS');
230
+ } catch (error) {
231
+ spinner.fail('Failed to pull images on VPS');
232
+ throw new Error(`Failed to pull images: ${error.message}`);
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Restart services on VPS
238
+ * @param {string} vpsUser - SSH username
239
+ * @param {string} vpsHost - VPS hostname or IP
240
+ * @param {string} vpsAppFolder - App folder path on VPS
241
+ * @returns {Promise<void>}
242
+ */
243
+ async function restartServicesOnVPS(vpsUser, vpsHost, vpsAppFolder) {
244
+ const ora = require('ora');
245
+
246
+ const spinner = ora('Restarting services...').start();
247
+
248
+ try {
249
+ await execAsync(
250
+ `ssh ${vpsUser}@${vpsHost} "cd ${vpsAppFolder}/infrastructure && docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d"`,
251
+ { timeout: 300000 } // 5 minutes
252
+ );
253
+ spinner.succeed('Services restarted');
254
+ } catch (error) {
255
+ spinner.fail('Failed to restart services');
256
+ throw new Error(`Failed to restart services: ${error.message}`);
257
+ }
258
+ }
259
+
212
260
  module.exports = {
213
261
  testSSHConnection,
214
262
  checkSSHKeys,
@@ -216,5 +264,7 @@ module.exports = {
216
264
  copyFileToVPS,
217
265
  copyDirectoryToVPS,
218
266
  checkRepoPrivacy,
219
- showDeployKeyInstructions
267
+ showDeployKeyInstructions,
268
+ pullImagesOnVPS,
269
+ restartServicesOnVPS
220
270
  };
@@ -15,6 +15,7 @@ const { replaceVariables } = require('./variable-replacer');
15
15
  const { getVariantConfig, getVariantsToApply } = require('../services/variant-config');
16
16
  const fs = require('fs-extra');
17
17
  const path = require('path');
18
+ const logger = require('./logger');
18
19
 
19
20
  /**
20
21
  * Process service with variant modifications
@@ -32,7 +33,7 @@ async function processServiceVariant(
32
33
  replacements,
33
34
  templateRoot
34
35
  ) {
35
- console.log(`\n๐Ÿ“ฆ Processing ${serviceName} with choices:`, variantChoices);
36
+ logger.detail(`Processing ${serviceName} with choices: ${JSON.stringify(variantChoices)}`, 2);
36
37
 
37
38
  const serviceConfig = getVariantConfig(serviceName);
38
39
  if (!serviceConfig) {
@@ -41,33 +42,35 @@ async function processServiceVariant(
41
42
 
42
43
  const basePath = path.join(templateRoot, serviceConfig.base);
43
44
 
44
- // Step 1: Copy base template (minimal - B2B + single-tenant)
45
- console.log(` ๐Ÿ“ Copying base template from ${serviceConfig.base}`);
45
+ // Copy base template
46
+ logger.detail(`Copying base template from ${serviceConfig.base}`, 2);
46
47
  await copyDirectory(basePath, destination, {
47
48
  exclude: ['node_modules', '.git', 'dist', '.env']
48
49
  });
49
50
 
50
- // Step 2: Determine which variants to apply
51
+ // Determine which variants to apply
51
52
  const variantsToApply = getVariantsToApply(variantChoices);
52
53
 
53
54
  if (variantsToApply.length === 0) {
54
- console.log(` โœ… Using base template (no variants to apply)`);
55
+ logger.detail('Using base template (no variants)', 2);
55
56
  } else {
56
- console.log(` ๐Ÿ”ง Applying variants: ${variantsToApply.join(', ')}`);
57
+ logger.detail(`Applying variants: ${variantsToApply.join(', ')}`, 2);
57
58
  }
58
59
 
59
- // Step 3: Apply each variant
60
+ // Apply each variant
60
61
  for (const variantName of variantsToApply) {
61
62
  const variantConfig = serviceConfig.variants[variantName];
62
63
 
63
64
  if (!variantConfig) {
64
- console.warn(` โš ๏ธ No configuration found for variant: ${variantName}, skipping`);
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);
65
68
  continue;
66
69
  }
67
70
 
68
- console.log(`\n โœจ Applying ${variantName} variant:`);
71
+ logger.detail(`Applying ${variantName} variant`, 2);
69
72
 
70
- // Step 3a: Copy variant FILES
73
+ // Copy variant FILES
71
74
  await copyVariantFiles(
72
75
  variantName,
73
76
  variantConfig.files || [],
@@ -76,7 +79,7 @@ async function processServiceVariant(
76
79
  templateRoot
77
80
  );
78
81
 
79
- // Step 3b: Insert variant SECTIONS
82
+ // Insert variant SECTIONS
80
83
  await insertVariantSections(
81
84
  variantName,
82
85
  variantConfig.sections || {},
@@ -86,15 +89,15 @@ async function processServiceVariant(
86
89
  );
87
90
  }
88
91
 
89
- // Step 4: Clean up unused section markers
90
- console.log(`\n ๐Ÿงน Cleaning up unused section markers`);
92
+ // Clean up unused section markers
93
+ logger.detail('Cleaning up unused section markers', 2);
91
94
  await cleanupSectionMarkers(serviceName, serviceConfig, variantsToApply, destination);
92
95
 
93
- // Step 5: Replace template variables ({{PROJECT_NAME}}, etc.)
94
- console.log(`\n ๐Ÿ”ค Replacing template variables`);
96
+ // Replace template variables
97
+ logger.detail('Replacing template variables', 2);
95
98
  await replaceVariables(destination, replacements);
96
99
 
97
- console.log(` โœ… ${serviceName} processing complete\n`);
100
+ logger.detail(`${serviceName} complete`, 2);
98
101
  }
99
102
 
100
103
  /**
@@ -110,7 +113,7 @@ async function cleanupSectionMarkers(serviceName, serviceConfig, appliedVariants
110
113
  const unappliedVariants = allVariants.filter(v => !appliedVariants.includes(v));
111
114
 
112
115
  if (unappliedVariants.length === 0) {
113
- console.log(` โœ“ No unused section markers to clean`);
116
+ logger.detail('No unused section markers to clean', 3);
114
117
  return;
115
118
  }
116
119
 
@@ -146,8 +149,6 @@ async function cleanupSectionMarkers(serviceName, serviceConfig, appliedVariants
146
149
  // Remove each unused section marker (keep content, remove only marker comments)
147
150
  for (const sectionName of sectionNames) {
148
151
  // Try all comment formats (// for JS/TS, {/* */} for JSX, # for YAML/Shell)
149
- // Capture: START marker, content, END marker - replace with just content
150
- // Include leading whitespace before markers to prevent indentation issues
151
152
  const slashPattern = new RegExp(
152
153
  `^[ \\t]*\\/\\/ ${sectionName}_START\\n([\\s\\S]*?)^[ \\t]*\\/\\/ ${sectionName}_END\\n?`,
153
154
  'gm'
@@ -185,14 +186,14 @@ async function cleanupSectionMarkers(serviceName, serviceConfig, appliedVariants
185
186
  totalCleaned++;
186
187
  }
187
188
  } catch (error) {
188
- console.warn(` โš ๏ธ Could not clean markers in ${filePath}:`, error.message);
189
+ logger.warn(`Could not clean markers in ${filePath}: ${error.message}`);
189
190
  }
190
191
  }
191
192
 
192
193
  if (totalCleaned > 0) {
193
- console.log(` โœ“ Cleaned up section markers in ${totalCleaned} file(s)`);
194
+ logger.detail(`Cleaned section markers in ${totalCleaned} file(s)`, 3);
194
195
  } else {
195
- console.log(` โœ“ No unused section markers found`);
196
+ logger.detail('No unused section markers found', 3);
196
197
  }
197
198
  }
198
199
 
@@ -206,11 +207,11 @@ async function cleanupSectionMarkers(serviceName, serviceConfig, appliedVariants
206
207
  */
207
208
  async function copyVariantFiles(variantName, files, filesDir, destination, templateRoot) {
208
209
  if (!files || files.length === 0) {
209
- console.log(` ๐Ÿ“‚ No files to copy for ${variantName}`);
210
+ logger.detail(`No files to copy for ${variantName}`, 3);
210
211
  return;
211
212
  }
212
213
 
213
- console.log(` ๐Ÿ“‚ Copying ${files.length} file(s)/folder(s):`);
214
+ logger.detail(`Copying ${files.length} file(s) for ${variantName}`, 3);
214
215
 
215
216
  const variantFilesPath = path.join(templateRoot, filesDir, variantName);
216
217
 
@@ -219,22 +220,18 @@ async function copyVariantFiles(variantName, files, filesDir, destination, templ
219
220
  const destPath = path.join(destination, filePath);
220
221
 
221
222
  try {
222
- // Check if source exists
223
223
  if (!await fs.pathExists(sourcePath)) {
224
- console.warn(` โš ๏ธ Source not found: ${filePath}, skipping`);
224
+ logger.warn(`Source not found: ${filePath}, skipping`);
225
225
  continue;
226
226
  }
227
227
 
228
- // Create parent directory if needed
229
228
  await fs.ensureDir(path.dirname(destPath));
230
-
231
- // Copy file or directory
232
229
  await fs.copy(sourcePath, destPath, { overwrite: true });
233
230
 
234
231
  const isDir = (await fs.stat(sourcePath)).isDirectory();
235
- console.log(` โœ“ Copied ${isDir ? 'folder' : 'file'}: ${filePath}`);
232
+ logger.detail(`Copied ${isDir ? 'folder' : 'file'}: ${filePath}`, 4);
236
233
  } catch (error) {
237
- console.warn(` โš ๏ธ Could not copy ${filePath}:`, error.message);
234
+ logger.warn(`Could not copy ${filePath}: ${error.message}`);
238
235
  }
239
236
  }
240
237
  }
@@ -249,45 +246,41 @@ async function copyVariantFiles(variantName, files, filesDir, destination, templ
249
246
  */
250
247
  async function insertVariantSections(variantName, sections, sectionsDir, destination, templateRoot) {
251
248
  if (!sections || Object.keys(sections).length === 0) {
252
- console.log(` โœ๏ธ No sections to insert for ${variantName}`);
249
+ logger.detail(`No sections to insert for ${variantName}`, 3);
253
250
  return;
254
251
  }
255
252
 
256
253
  const sectionFiles = Object.keys(sections);
257
- console.log(` โœ๏ธ Inserting sections into ${sectionFiles.length} file(s):`);
254
+ logger.detail(`Inserting sections into ${sectionFiles.length} file(s)`, 3);
258
255
 
259
256
  const variantSectionsPath = path.join(templateRoot, sectionsDir, variantName);
260
257
 
261
258
  for (const [filePath, sectionNames] of Object.entries(sections)) {
262
259
  const targetFilePath = path.join(destination, filePath);
263
260
 
264
- // Check if target file exists
265
261
  if (!await fs.pathExists(targetFilePath)) {
266
- console.warn(` โš ๏ธ Target file not found: ${filePath}, skipping sections`);
262
+ logger.warn(`Target file not found: ${filePath}, skipping sections`);
267
263
  continue;
268
264
  }
269
265
 
270
- console.log(` ๐Ÿ“ ${filePath}:`);
266
+ logger.detail(`Processing ${filePath}`, 4);
271
267
 
272
268
  for (const sectionName of sectionNames) {
273
269
  try {
274
- // Read section content from file
275
270
  const fileName = path.basename(filePath);
276
271
  const sectionFileName = `${fileName}.${sectionName}`;
277
272
  const sectionFilePath = path.join(variantSectionsPath, sectionFileName);
278
273
 
279
274
  if (!await fs.pathExists(sectionFilePath)) {
280
- console.warn(` โš ๏ธ Section file not found: ${sectionFileName}, skipping`);
275
+ logger.warn(`Section file not found: ${sectionFileName}, skipping`);
281
276
  continue;
282
277
  }
283
278
 
284
279
  const sectionContent = await fs.readFile(sectionFilePath, 'utf-8');
285
-
286
- // Insert section content into target file
287
280
  await replaceSection(targetFilePath, sectionName, sectionContent);
288
- console.log(` โœ“ Inserted [${sectionName}]`);
281
+ logger.detail(`Inserted [${sectionName}]`, 5);
289
282
  } catch (error) {
290
- console.warn(` โš ๏ธ Could not insert section ${sectionName}:`, error.message);
283
+ logger.warn(`Could not insert section ${sectionName}: ${error.message}`);
291
284
  }
292
285
  }
293
286
  }