plusui-native 0.2.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/README.md +162 -0
- package/package.json +36 -0
- package/src/assets/icon-generator.js +251 -0
- package/src/assets/resource-embedder.js +351 -0
- package/src/doctor/detectors/cmake.js +84 -0
- package/src/doctor/detectors/compiler.js +145 -0
- package/src/doctor/detectors/just.js +45 -0
- package/src/doctor/detectors/nodejs.js +57 -0
- package/src/doctor/detectors/webview2.js +66 -0
- package/src/doctor/index.js +184 -0
- package/src/doctor/installers/linux.js +121 -0
- package/src/doctor/installers/macos.js +123 -0
- package/src/doctor/installers/windows.js +117 -0
- package/src/doctor/reporter.js +219 -0
- package/src/index.js +904 -0
- package/templates/base/Justfile +115 -0
- package/templates/base/README.md.template +168 -0
- package/templates/base/assets/README.md +88 -0
- package/templates/base/assets/icon.png +0 -0
- package/templates/manager.js +217 -0
- package/templates/react/.vscode/c_cpp_properties.json +24 -0
- package/templates/react/CMakeLists.txt.template +151 -0
- package/templates/react/frontend/index.html +12 -0
- package/templates/react/frontend/package.json.template +24 -0
- package/templates/react/frontend/src/App.tsx +134 -0
- package/templates/react/frontend/src/main.tsx +10 -0
- package/templates/react/frontend/src/styles/app.css +140 -0
- package/templates/react/frontend/tsconfig.json +21 -0
- package/templates/react/frontend/tsconfig.node.json +11 -0
- package/templates/react/frontend/vite.config.ts +14 -0
- package/templates/react/main.cpp.template +201 -0
- package/templates/react/package.json.template +23 -0
- package/templates/solid/.vscode/c_cpp_properties.json +24 -0
- package/templates/solid/CMakeLists.txt.template +151 -0
- package/templates/solid/frontend/index.html +12 -0
- package/templates/solid/frontend/package.json.template +21 -0
- package/templates/solid/frontend/src/App.tsx +133 -0
- package/templates/solid/frontend/src/main.tsx +5 -0
- package/templates/solid/frontend/src/styles/app.css +140 -0
- package/templates/solid/frontend/tsconfig.json +22 -0
- package/templates/solid/frontend/tsconfig.node.json +11 -0
- package/templates/solid/frontend/vite.config.ts +14 -0
- package/templates/solid/main.cpp.template +192 -0
- package/templates/solid/package.json.template +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# PlusUI
|
|
2
|
+
|
|
3
|
+
Build C++ desktop apps with web tech. Like Tauri, but for C++.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install CLI globally
|
|
9
|
+
npm install -g plusui-cli
|
|
10
|
+
|
|
11
|
+
# Create a new project
|
|
12
|
+
plusui create myapp
|
|
13
|
+
cd myapp
|
|
14
|
+
|
|
15
|
+
# Install dependencies
|
|
16
|
+
npm install
|
|
17
|
+
|
|
18
|
+
# Run in development mode (with HMR)
|
|
19
|
+
npm run dev
|
|
20
|
+
|
|
21
|
+
# Build for production
|
|
22
|
+
npm run build
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Project Structure
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
myapp/
|
|
29
|
+
├── src/
|
|
30
|
+
│ ├── main.cpp # C++ backend
|
|
31
|
+
│ ├── index.html # Frontend entry
|
|
32
|
+
│ └── ...
|
|
33
|
+
├── plusui-core/ # Framework (git submodule)
|
|
34
|
+
├── package.json
|
|
35
|
+
└── CMakeLists.txt
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## C++ API
|
|
39
|
+
|
|
40
|
+
```cpp
|
|
41
|
+
#include <plusui/app.hpp>
|
|
42
|
+
|
|
43
|
+
int main() {
|
|
44
|
+
auto wv = plusui::createApp()
|
|
45
|
+
.title("My App")
|
|
46
|
+
.width(1024)
|
|
47
|
+
.height(768)
|
|
48
|
+
.build();
|
|
49
|
+
|
|
50
|
+
// Navigate to frontend
|
|
51
|
+
wv.navigate("file://${CMAKE_CURRENT_SOURCE_DIR}/src/index.html");
|
|
52
|
+
|
|
53
|
+
// Listen for events from frontend
|
|
54
|
+
wv.on("greet", [](const std::string& name) {
|
|
55
|
+
std::cout << "Hello, " << name << "!" << std::endl;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Run the app
|
|
59
|
+
plusui::createApp().run();
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## TypeScript API
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { createPlusUI } from '@plusui/core';
|
|
68
|
+
|
|
69
|
+
const plusui = createPlusUI();
|
|
70
|
+
|
|
71
|
+
// Call C++ function
|
|
72
|
+
const result = await plusui.invoke<number>('add', [1, 2]);
|
|
73
|
+
|
|
74
|
+
// Listen for events
|
|
75
|
+
plusui.on('server-event', (data) => {
|
|
76
|
+
console.log('Received:', data);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Emit event to C++
|
|
80
|
+
plusui.emit('greet', 'World');
|
|
81
|
+
|
|
82
|
+
// One-time listener
|
|
83
|
+
plusui.once('ready', () => {
|
|
84
|
+
console.log('App ready!');
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Events vs Invoke
|
|
89
|
+
|
|
90
|
+
| Method | Direction | Use Case |
|
|
91
|
+
|--------|-----------|----------|
|
|
92
|
+
| `invoke()` | Frontend → Backend | Request/response |
|
|
93
|
+
| `emit()` | Frontend → Backend | Fire-and-forget |
|
|
94
|
+
| `on()` | Bidirectional | Subscriptions |
|
|
95
|
+
|
|
96
|
+
## Configuration
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
const plusui = createPlusUI({
|
|
100
|
+
title: 'My App',
|
|
101
|
+
width: 800,
|
|
102
|
+
height: 600,
|
|
103
|
+
resizable: true,
|
|
104
|
+
devtools: true,
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Building
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Development (auto-rebuild + HMR)
|
|
112
|
+
npm run dev
|
|
113
|
+
|
|
114
|
+
# Production build
|
|
115
|
+
npm run build
|
|
116
|
+
|
|
117
|
+
# Run built app
|
|
118
|
+
npm run start
|
|
119
|
+
|
|
120
|
+
# Regenerate bindings manually (app-level)
|
|
121
|
+
npm run bind
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Bindgen runs per app project (typically `src/features` -> `src/Bindings_Generated`).
|
|
125
|
+
`dev` and all `build` commands auto-refresh bindings first when `src/features` exists.
|
|
126
|
+
|
|
127
|
+
## Requirements
|
|
128
|
+
|
|
129
|
+
- **Windows**: WebView2 Runtime (pre-installed on Win10/11)
|
|
130
|
+
- **macOS**: Xcode Command Line Tools
|
|
131
|
+
- **Linux**: WebKit2GTK (`sudo apt install libwebkit2gtk-4.0-dev`)
|
|
132
|
+
|
|
133
|
+
## Architecture
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
┌─────────────────────────────────────┐
|
|
137
|
+
│ Frontend (Web) │
|
|
138
|
+
│ React / Vue / Svelte / Vanilla │
|
|
139
|
+
└─────────────────┬───────────────────┘
|
|
140
|
+
│ Events / Invoke
|
|
141
|
+
┌─────────────────▼───────────────────┐
|
|
142
|
+
│ TypeScript SDK │
|
|
143
|
+
│ @plusui/core │
|
|
144
|
+
└─────────────────┬───────────────────┘
|
|
145
|
+
│ JSON IPC
|
|
146
|
+
┌─────────────────▼───────────────────┐
|
|
147
|
+
│ C++ Core │
|
|
148
|
+
│ plusui::createApp() │
|
|
149
|
+
│ webview.h │
|
|
150
|
+
└─────────────────┬───────────────────┘
|
|
151
|
+
│
|
|
152
|
+
┌─────────────────▼───────────────────┐
|
|
153
|
+
│ Native WebView │
|
|
154
|
+
│ Windows: WebView2 (Edge) │
|
|
155
|
+
│ macOS: WKWebView (WebKit) │
|
|
156
|
+
│ Linux: WebKitGTK │
|
|
157
|
+
└─────────────────────────────────────┘
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "plusui-native",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "PlusUI CLI - Build C++ desktop apps with web tech",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"plusui": "./src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/index.js",
|
|
12
|
+
"dev": "node src/index.js dev"
|
|
13
|
+
},
|
|
14
|
+
"keywords": ["desktop", "cpp", "webview", "gui"],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"chokidar": "^3.5.3",
|
|
18
|
+
"vite": "^5.0.0",
|
|
19
|
+
"commander": "^12.0.0",
|
|
20
|
+
"chalk": "^5.3.0",
|
|
21
|
+
"ora": "^8.0.0",
|
|
22
|
+
"semver": "^7.6.0",
|
|
23
|
+
"which": "^4.0.0",
|
|
24
|
+
"execa": "^8.0.1",
|
|
25
|
+
"@plusui-native/builder": "*",
|
|
26
|
+
"@plusui-native/bindgen": "*"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"plusui-native-core": "*",
|
|
30
|
+
"plusui-native-bindgen": "*"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"preferGlobal": true
|
|
36
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Icon Generator for PlusUI
|
|
5
|
+
* Generates all platform-specific icon formats from a single source icon
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - Windows: .ico (16x16, 32x32, 48x48, 64x64, 128x128, 256x256)
|
|
9
|
+
* - macOS: .icns (16x16@1x/2x, 32x32@1x/2x, 128x128@1x/2x, 256x256@1x/2x, 512x512@1x/2x)
|
|
10
|
+
* - Linux: PNG (16x16, 32x32, 48x48, 64x64, 128x128, 256x256, 512x512)
|
|
11
|
+
* - Tray icons: 16x16, 32x32 (for system tray)
|
|
12
|
+
* - Taskbar/Title bar: 32x32, 48x48
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
16
|
+
import { existsSync } from 'fs';
|
|
17
|
+
import { join, basename, extname } from 'path';
|
|
18
|
+
import { execSync } from 'child_process';
|
|
19
|
+
|
|
20
|
+
const SIZES = {
|
|
21
|
+
windows: [16, 32, 48, 64, 128, 256],
|
|
22
|
+
macos: [16, 32, 128, 256, 512],
|
|
23
|
+
linux: [16, 32, 48, 64, 128, 256, 512],
|
|
24
|
+
tray: [16, 32],
|
|
25
|
+
taskbar: [32, 48],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
class IconGenerator {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.hasSharp = this.checkSharp();
|
|
31
|
+
this.hasImageMagick = this.checkImageMagick();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
checkSharp() {
|
|
35
|
+
try {
|
|
36
|
+
require.resolve('sharp');
|
|
37
|
+
return true;
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
checkImageMagick() {
|
|
44
|
+
try {
|
|
45
|
+
execSync('magick -version', { stdio: 'ignore' });
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
try {
|
|
49
|
+
execSync('convert -version', { stdio: 'ignore' });
|
|
50
|
+
return true;
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async checkDependencies() {
|
|
58
|
+
if (!this.hasSharp && !this.hasImageMagick) {
|
|
59
|
+
console.log('⚠️ No image processor found.');
|
|
60
|
+
console.log('\nOption 1 (Recommended): Install Sharp');
|
|
61
|
+
console.log(' npm install sharp --save-dev');
|
|
62
|
+
console.log('\nOption 2: Install ImageMagick');
|
|
63
|
+
console.log(' Windows: winget install ImageMagick.ImageMagick');
|
|
64
|
+
console.log(' macOS: brew install imagemagick');
|
|
65
|
+
console.log(' Linux: sudo apt install imagemagick');
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async resizeWithSharp(inputPath, outputPath, size) {
|
|
72
|
+
const sharp = require('sharp');
|
|
73
|
+
await sharp(inputPath)
|
|
74
|
+
.resize(size, size, {
|
|
75
|
+
fit: 'contain',
|
|
76
|
+
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
|
77
|
+
})
|
|
78
|
+
.png()
|
|
79
|
+
.toFile(outputPath);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async resizeWithImageMagick(inputPath, outputPath, size) {
|
|
83
|
+
const cmd = this.hasImageMagick && execSync('which magick 2>/dev/null', { encoding: 'utf8' }).trim()
|
|
84
|
+
? 'magick'
|
|
85
|
+
: 'convert';
|
|
86
|
+
|
|
87
|
+
execSync(
|
|
88
|
+
`${cmd} "${inputPath}" -resize ${size}x${size} -background none -gravity center -extent ${size}x${size} "${outputPath}"`,
|
|
89
|
+
{ stdio: 'ignore' }
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async resize(inputPath, outputPath, size) {
|
|
94
|
+
if (this.hasSharp) {
|
|
95
|
+
await this.resizeWithSharp(inputPath, outputPath, size);
|
|
96
|
+
} else {
|
|
97
|
+
await this.resizeWithImageMagick(inputPath, outputPath, size);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async generateWindowsIco(inputPath, outputPath) {
|
|
102
|
+
console.log(' 📦 Generating Windows .ico...');
|
|
103
|
+
const tempDir = join(process.cwd(), '.icon-temp');
|
|
104
|
+
await mkdir(tempDir, { recursive: true });
|
|
105
|
+
|
|
106
|
+
const pngPaths = [];
|
|
107
|
+
for (const size of SIZES.windows) {
|
|
108
|
+
const pngPath = join(tempDir, `icon_${size}.png`);
|
|
109
|
+
await this.resize(inputPath, pngPath, size);
|
|
110
|
+
pngPaths.push(pngPath);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Try to use ImageMagick to create .ico
|
|
114
|
+
if (this.hasImageMagick) {
|
|
115
|
+
try {
|
|
116
|
+
const cmd = execSync('which magick 2>/dev/null', { encoding: 'utf8' }).trim() ? 'magick' : 'convert';
|
|
117
|
+
execSync(`${cmd} ${pngPaths.join(' ')} "${outputPath}"`, { stdio: 'ignore' });
|
|
118
|
+
console.log(' ✓ Created app.ico');
|
|
119
|
+
} catch (e) {
|
|
120
|
+
console.log(' ⚠️ Could not create .ico, using PNG fallback');
|
|
121
|
+
// Copy largest PNG as fallback
|
|
122
|
+
const fallback = pngPaths[pngPaths.length - 1];
|
|
123
|
+
await writeFile(outputPath.replace('.ico', '.png'), await readFile(fallback));
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
// Just copy PNGs
|
|
127
|
+
for (let i = 0; i < SIZES.windows.length; i++) {
|
|
128
|
+
const size = SIZES.windows[i];
|
|
129
|
+
const dest = outputPath.replace('.ico', `_${size}.png`);
|
|
130
|
+
await writeFile(dest, await readFile(pngPaths[i]));
|
|
131
|
+
console.log(` ✓ Created icon_${size}.png`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Cleanup temp
|
|
136
|
+
for (const p of pngPaths) {
|
|
137
|
+
try { await rm(p); } catch {}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async generateMacOSIcns(inputPath, outputPath) {
|
|
142
|
+
console.log(' 🍎 Generating macOS .icns...');
|
|
143
|
+
const iconsetDir = outputPath.replace('.icns', '.iconset');
|
|
144
|
+
await mkdir(iconsetDir, { recursive: true });
|
|
145
|
+
|
|
146
|
+
const sizes = [
|
|
147
|
+
[16, 'icon_16x16.png'],
|
|
148
|
+
[32, 'icon_16x16@2x.png'],
|
|
149
|
+
[32, 'icon_32x32.png'],
|
|
150
|
+
[64, 'icon_32x32@2x.png'],
|
|
151
|
+
[128, 'icon_128x128.png'],
|
|
152
|
+
[256, 'icon_128x128@2x.png'],
|
|
153
|
+
[256, 'icon_256x256.png'],
|
|
154
|
+
[512, 'icon_256x256@2x.png'],
|
|
155
|
+
[512, 'icon_512x512.png'],
|
|
156
|
+
[1024, 'icon_512x512@2x.png'],
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
for (const [size, filename] of sizes) {
|
|
160
|
+
const dest = join(iconsetDir, filename);
|
|
161
|
+
await this.resize(inputPath, dest, size);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Try to create .icns on macOS
|
|
165
|
+
if (process.platform === 'darwin') {
|
|
166
|
+
try {
|
|
167
|
+
execSync(`iconutil -c icns "${iconsetDir}" -o "${outputPath}"`, { stdio: 'ignore' });
|
|
168
|
+
console.log(' ✓ Created app.icns');
|
|
169
|
+
} catch {
|
|
170
|
+
console.log(' ⚠️ Could not create .icns, keeping .iconset folder');
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
console.log(' ℹ️ .iconset folder created (use iconutil on macOS to convert)');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async generateLinuxIcons(inputPath, outputDir) {
|
|
178
|
+
console.log(' 🐧 Generating Linux icons...');
|
|
179
|
+
await mkdir(outputDir, { recursive: true });
|
|
180
|
+
|
|
181
|
+
for (const size of SIZES.linux) {
|
|
182
|
+
const dest = join(outputDir, `icon_${size}x${size}.png`);
|
|
183
|
+
await this.resize(inputPath, dest, size);
|
|
184
|
+
console.log(` ✓ Created icon_${size}x${size}.png`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async generateTrayIcons(inputPath, outputDir) {
|
|
189
|
+
console.log(' 🔔 Generating tray icons...');
|
|
190
|
+
await mkdir(outputDir, { recursive: true });
|
|
191
|
+
|
|
192
|
+
for (const size of SIZES.tray) {
|
|
193
|
+
const dest = join(outputDir, `tray_${size}x${size}.png`);
|
|
194
|
+
await this.resize(inputPath, dest, size);
|
|
195
|
+
console.log(` ✓ Created tray_${size}x${size}.png`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async generate(inputPath, outputBase) {
|
|
200
|
+
console.log(`\n🎨 Generating icons from: ${basename(inputPath)}\n`);
|
|
201
|
+
|
|
202
|
+
if (!existsSync(inputPath)) {
|
|
203
|
+
throw new Error(`Input icon not found: ${inputPath}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const hasDeps = await this.checkDependencies();
|
|
207
|
+
if (!hasDeps) {
|
|
208
|
+
throw new Error('Missing dependencies');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Create output directories
|
|
212
|
+
const dirs = {
|
|
213
|
+
windows: join(outputBase, 'windows'),
|
|
214
|
+
macos: join(outputBase, 'macos'),
|
|
215
|
+
linux: join(outputBase, 'linux'),
|
|
216
|
+
tray: join(outputBase, 'tray'),
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
for (const dir of Object.values(dirs)) {
|
|
220
|
+
await mkdir(dir, { recursive: true });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Generate platform-specific icons
|
|
224
|
+
await this.generateWindowsIco(inputPath, join(dirs.windows, 'app.ico'));
|
|
225
|
+
await this.generateMacOSIcns(inputPath, join(dirs.macos, 'app.icns'));
|
|
226
|
+
await this.generateLinuxIcons(inputPath, dirs.linux);
|
|
227
|
+
await this.generateTrayIcons(inputPath, dirs.tray);
|
|
228
|
+
|
|
229
|
+
console.log('\n✨ Icon generation complete!\n');
|
|
230
|
+
console.log('Generated icons:');
|
|
231
|
+
console.log(` 📁 ${dirs.windows}`);
|
|
232
|
+
console.log(` 📁 ${dirs.macos}`);
|
|
233
|
+
console.log(` 📁 ${dirs.linux}`);
|
|
234
|
+
console.log(` 📁 ${dirs.tray}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// CLI
|
|
239
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
240
|
+
const args = process.argv.slice(2);
|
|
241
|
+
const inputPath = args[0] || join(process.cwd(), 'assets', 'icon.png');
|
|
242
|
+
const outputBase = args[1] || join(process.cwd(), 'assets', 'icons');
|
|
243
|
+
|
|
244
|
+
const generator = new IconGenerator();
|
|
245
|
+
generator.generate(inputPath, outputBase).catch(err => {
|
|
246
|
+
console.error('❌ Error:', err.message);
|
|
247
|
+
process.exit(1);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export { IconGenerator };
|