@launchframe/cli 1.0.0-beta.34 → 1.0.0-beta.35

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@launchframe/cli",
3
- "version": "1.0.0-beta.34",
3
+ "version": "1.0.0-beta.35",
4
4
  "description": "Production-ready B2B SaaS boilerplate with subscriptions, credits, and multi-tenancy",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -40,10 +40,12 @@
40
40
  "access": "public"
41
41
  },
42
42
  "dependencies": {
43
+ "@resvg/resvg-js": "^2.6.2",
43
44
  "bcryptjs": "^2.4.3",
44
45
  "chalk": "^4.1.2",
45
46
  "dotenv": "^17.3.1",
46
47
  "fs-extra": "^11.1.1",
47
- "inquirer": "^8.2.5"
48
+ "inquirer": "^8.2.5",
49
+ "to-ico": "^1.1.5"
48
50
  }
49
51
  }
@@ -0,0 +1,160 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const { requireProject, getProjectConfig } = require('../utils/project-helpers');
5
+
6
+ /**
7
+ * Render SVG to PNG buffer at a given size using @resvg/resvg-js
8
+ * @param {Buffer} svgBuffer - SVG file buffer
9
+ * @param {number} size - Width/height in pixels
10
+ * @returns {Buffer} PNG buffer
11
+ */
12
+ function renderPng(Resvg, svgBuffer, size) {
13
+ const resvg = new Resvg(svgBuffer, {
14
+ fitTo: { mode: 'width', value: size },
15
+ });
16
+ const pngData = resvg.render();
17
+ return pngData.asPng();
18
+ }
19
+
20
+ /**
21
+ * Write a file, creating directories as needed
22
+ * @param {string} filePath - Absolute path to write
23
+ * @param {Buffer|string} data - File content
24
+ */
25
+ function writeFile(filePath, data) {
26
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
27
+ fs.writeFileSync(filePath, data);
28
+ console.log(chalk.gray(` ✓ ${filePath.replace(process.cwd() + '/', '')}`));
29
+ }
30
+
31
+ /**
32
+ * Generate and inject logo/favicon assets across all relevant frontend services
33
+ */
34
+ async function devLogo() {
35
+ requireProject();
36
+
37
+ const cwd = process.cwd();
38
+ const logoPath = path.join(cwd, 'logo.svg');
39
+
40
+ if (!fs.existsSync(logoPath)) {
41
+ console.error(chalk.red('\n❌ Error: logo.svg not found'));
42
+ console.log(chalk.gray('Place your logo.svg file in the project root:'));
43
+ console.log(chalk.white(` ${cwd}/logo.svg`));
44
+ console.log();
45
+ console.log(chalk.gray('Then run:'));
46
+ console.log(chalk.white(' launchframe dev:logo\n'));
47
+ process.exit(1);
48
+ }
49
+
50
+ const config = getProjectConfig();
51
+ const hasCustomersPortal = (config.installedServices || []).includes('customers-portal');
52
+
53
+ // Lazy load heavy dependencies
54
+ const { Resvg } = require('@resvg/resvg-js');
55
+ const toIco = require('to-ico');
56
+
57
+ const svgBuffer = fs.readFileSync(logoPath);
58
+ const svgContent = svgBuffer.toString('utf8');
59
+
60
+ console.log(chalk.blue.bold('\nGenerating logo assets...\n'));
61
+
62
+ // ─── Website ─────────────────────────────────────────────────────────────
63
+ const websitePath = path.join(cwd, 'website');
64
+ if (!fs.existsSync(websitePath)) {
65
+ console.log(chalk.yellow('⚠ website not found — skipping'));
66
+ } else {
67
+ console.log(chalk.white('website/public/'));
68
+ const pub = path.join(websitePath, 'public');
69
+ const images = path.join(pub, 'images');
70
+
71
+ const png16 = renderPng(Resvg, svgBuffer, 16);
72
+ const png32 = renderPng(Resvg, svgBuffer, 32);
73
+ const png96 = renderPng(Resvg, svgBuffer, 96);
74
+ const png180 = renderPng(Resvg, svgBuffer, 180);
75
+ const png512 = renderPng(Resvg, svgBuffer, 512);
76
+ const icoBuffer = await toIco([png16, png32]);
77
+
78
+ writeFile(path.join(pub, 'favicon.ico'), icoBuffer);
79
+ writeFile(path.join(pub, 'favicon.svg'), svgContent);
80
+ writeFile(path.join(pub, 'favicon.png'), png32);
81
+ writeFile(path.join(pub, 'favicon-96x96.png'), png96);
82
+ writeFile(path.join(pub, 'apple-touch-icon.png'), png180);
83
+ writeFile(path.join(images, 'logo.svg'), svgContent);
84
+ writeFile(path.join(images, 'logo.png'), png512);
85
+ }
86
+
87
+ // ─── Admin Portal ─────────────────────────────────────────────────────────
88
+ const adminPath = path.join(cwd, 'admin-portal');
89
+ if (!fs.existsSync(adminPath)) {
90
+ console.log(chalk.yellow('⚠ admin-portal not found — skipping'));
91
+ } else {
92
+ console.log(chalk.white('\nadmin-portal/public/'));
93
+ const pub = path.join(adminPath, 'public');
94
+ const favicons = path.join(pub, 'favicons');
95
+ const srcAssets = path.join(adminPath, 'src', 'assets');
96
+
97
+ const png16 = renderPng(Resvg, svgBuffer, 16);
98
+ const png24 = renderPng(Resvg, svgBuffer, 24);
99
+ const png32 = renderPng(Resvg, svgBuffer, 32);
100
+ const png64 = renderPng(Resvg, svgBuffer, 64);
101
+ const png96 = renderPng(Resvg, svgBuffer, 96);
102
+ const png180 = renderPng(Resvg, svgBuffer, 180);
103
+ const png192 = renderPng(Resvg, svgBuffer, 192);
104
+ const png512 = renderPng(Resvg, svgBuffer, 512);
105
+ const icoBuffer = await toIco([png16, png32]);
106
+ const icoFavicons = await toIco([png16, png24, png32, png64]);
107
+
108
+ writeFile(path.join(pub, 'favicon.ico'), icoBuffer);
109
+ writeFile(path.join(pub, 'favicon.svg'), svgContent);
110
+ writeFile(path.join(pub, 'favicon.png'), png32);
111
+ writeFile(path.join(favicons, 'favicon.svg'), svgContent);
112
+ writeFile(path.join(favicons, 'favicon.ico'), icoFavicons);
113
+ writeFile(path.join(favicons, 'favicon-16x16.png'), png16);
114
+ writeFile(path.join(favicons, 'favicon-32x32.png'), png32);
115
+ writeFile(path.join(favicons, 'favicon-96x96.png'), png96);
116
+ writeFile(path.join(favicons, 'web-app-manifest-192x192.png'), png192);
117
+ writeFile(path.join(favicons, 'web-app-manifest-96x96.png'), png96);
118
+ writeFile(path.join(favicons, 'web-app-manifest-512x512.png'), png512);
119
+ writeFile(path.join(favicons, 'apple-touch-icon.png'), png180);
120
+ writeFile(path.join(pub, 'logo.svg'), svgContent);
121
+ writeFile(path.join(pub, 'logo.png'), png512);
122
+ writeFile(path.join(pub, 'logo192.png'), png192);
123
+ writeFile(path.join(pub, 'logo512.png'), png512);
124
+ writeFile(path.join(srcAssets, 'logo.svg'), svgContent);
125
+ }
126
+
127
+ // ─── Customers Portal (B2B2C only) ────────────────────────────────────────
128
+ if (hasCustomersPortal) {
129
+ const customersPath = path.join(cwd, 'customers-portal');
130
+ if (!fs.existsSync(customersPath)) {
131
+ console.log(chalk.yellow('\n⚠ customers-portal not found — skipping'));
132
+ } else {
133
+ console.log(chalk.white('\ncustomers-portal/public/'));
134
+ const pub = path.join(customersPath, 'public');
135
+ const favicons = path.join(pub, 'favicons');
136
+
137
+ const png16 = renderPng(Resvg, svgBuffer, 16);
138
+ const png32 = renderPng(Resvg, svgBuffer, 32);
139
+ const png96 = renderPng(Resvg, svgBuffer, 96);
140
+ const png180 = renderPng(Resvg, svgBuffer, 180);
141
+ const png512 = renderPng(Resvg, svgBuffer, 512);
142
+ const icoBuffer = await toIco([png16, png32]);
143
+
144
+ writeFile(path.join(pub, 'favicon.ico'), icoBuffer);
145
+ writeFile(path.join(pub, 'favicon.svg'), svgContent);
146
+ writeFile(path.join(pub, 'favicon.png'), png32);
147
+ writeFile(path.join(favicons, 'favicon.svg'), svgContent);
148
+ writeFile(path.join(favicons, 'favicon-16x16.png'), png16);
149
+ writeFile(path.join(favicons, 'favicon-32x32.png'), png32);
150
+ writeFile(path.join(favicons, 'favicon-96x96.png'), png96);
151
+ writeFile(path.join(favicons, 'apple-touch-icon.png'), png180);
152
+ writeFile(path.join(pub, 'logo.svg'), svgContent);
153
+ writeFile(path.join(pub, 'logo.png'), png512);
154
+ }
155
+ }
156
+
157
+ console.log(chalk.green('\n✅ Logo assets generated successfully!\n'));
158
+ }
159
+
160
+ module.exports = { devLogo };
@@ -61,7 +61,8 @@ function help() {
61
61
  console.log(chalk.gray(' cache:clear Delete cache (re-download on next use)\n'));
62
62
  console.log(chalk.white('Dev Helpers:'));
63
63
  console.log(chalk.gray(' dev:add-user Generate and insert a random test user into the local database'));
64
- console.log(chalk.gray(' dev:queue Open the Bull queue dashboard in the browser\n'));
64
+ console.log(chalk.gray(' dev:queue Open the Bull queue dashboard in the browser'));
65
+ console.log(chalk.gray(' dev:logo Generate and inject favicon/logo assets from logo.svg\n'));
65
66
  console.log(chalk.white('Other commands:'));
66
67
  console.log(chalk.gray(' doctor Check project health and configuration'));
67
68
  console.log(chalk.gray(' telemetry Show telemetry status'));
package/src/index.js CHANGED
@@ -45,6 +45,7 @@ const { moduleAdd, moduleList } = require('./commands/module');
45
45
  const { cacheClear, cacheInfo, cacheUpdate } = require('./commands/cache');
46
46
  const { devAddUser } = require('./commands/dev-add-user');
47
47
  const { devQueue } = require('./commands/dev-queue');
48
+ const { devLogo } = require('./commands/dev-logo');
48
49
  const { deploySyncFeatures } = require('./commands/deploy-sync-features');
49
50
 
50
51
  // Get command and arguments
@@ -219,6 +220,9 @@ async function main() {
219
220
  case 'dev:queue':
220
221
  await devQueue();
221
222
  break;
223
+ case 'dev:logo':
224
+ await devLogo();
225
+ break;
222
226
  case 'telemetry':
223
227
  if (flags.disable) {
224
228
  setTelemetryEnabled(false);