@launchframe/cli 1.0.0-beta.34 → 1.0.0-beta.36
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 +4 -2
- package/src/commands/deploy-configure.js +5 -1
- package/src/commands/dev-logo.js +160 -0
- package/src/commands/help.js +2 -1
- package/src/index.js +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@launchframe/cli",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.36",
|
|
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
|
}
|
|
@@ -53,7 +53,8 @@ async function deployConfigure() {
|
|
|
53
53
|
'{{PRIMARY_DOMAIN}}': deployAnswers.primaryDomain,
|
|
54
54
|
'{{ADMIN_EMAIL}}': deployAnswers.adminEmail,
|
|
55
55
|
'{{GITHUB_ORG}}': deployAnswers.githubOrg,
|
|
56
|
-
'{{VPS_APP_FOLDER}}': deployAnswers.vpsAppFolder
|
|
56
|
+
'{{VPS_APP_FOLDER}}': deployAnswers.vpsAppFolder,
|
|
57
|
+
'{{PROJECT_NAME}}': config.projectName
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
console.log(chalk.yellow('\n⚙️ Updating configuration files...\n'));
|
|
@@ -74,6 +75,9 @@ async function deployConfigure() {
|
|
|
74
75
|
'admin-portal/src/App.tsx',
|
|
75
76
|
'admin-portal/src/components/common/PageTitle.tsx',
|
|
76
77
|
'admin-portal/src/sentry.tsx',
|
|
78
|
+
'backend/.github/workflows/deploy-backend.yml',
|
|
79
|
+
'admin-portal/.github/workflows/deploy-admin-portal.yml',
|
|
80
|
+
'website/.github/workflows/deploy-website.yml',
|
|
77
81
|
];
|
|
78
82
|
|
|
79
83
|
if (config.variants.tenancy === 'multi-tenant') {
|
|
@@ -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 };
|
package/src/commands/help.js
CHANGED
|
@@ -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
|
|
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);
|