@lotus-gui/dev 0.2.1 → 0.2.2
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 +28 -43
- package/bin/lotus.js +504 -211
- package/package.json +6 -7
- package/sea-config.json +1 -0
- package/shim.js +1 -0
package/README.md
CHANGED
|
@@ -65,36 +65,29 @@ lotus dev main.js # Custom entry point
|
|
|
65
65
|
|
|
66
66
|
### `lotus build`
|
|
67
67
|
|
|
68
|
-
Build your application into a distributable installer package.
|
|
68
|
+
Build your application into a native, single-executable distributable installer package using Node SEA and CrabNebula.
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
|
-
lotus build --
|
|
72
|
-
lotus build --
|
|
71
|
+
lotus build --target appimage
|
|
72
|
+
lotus build --target deb
|
|
73
|
+
lotus build --target exe
|
|
73
74
|
```
|
|
74
75
|
|
|
75
76
|
| Flag | Values | Default | Description |
|
|
76
77
|
|------|--------|---------|-------------|
|
|
77
|
-
| `--
|
|
78
|
-
| `--target` | `deb`, `rpm` | `deb` | Installer format (Linux only) |
|
|
78
|
+
| `--target` | `deb`, `appimage`, `pacman`, `msi`, `exe`, `dmg`, `app` | `deb` | Target installer format. Note: Windows targets (`msi`, `exe`) require building on a Windows host or properly configured cross-compilation environment. |
|
|
79
79
|
|
|
80
80
|
**What it does:**
|
|
81
|
-
1. Reads `lotus.config.json` from the current directory
|
|
82
|
-
2.
|
|
83
|
-
3.
|
|
84
|
-
4. Generates a
|
|
85
|
-
5.
|
|
86
|
-
6.
|
|
81
|
+
1. Reads `lotus.config.json` from the current directory.
|
|
82
|
+
2. Recursively bundles your application JS using `esbuild`.
|
|
83
|
+
3. Discovers native `.node` modules and copies them out of the bundle.
|
|
84
|
+
4. Generates a Node Single Executable Application (SEA) blob and injects it into a Node.js binary.
|
|
85
|
+
5. Invokes `@crabnebula/packager` to wrap the executable and native modules into the final OS-specific installer.
|
|
86
|
+
6. Build artifacts go to `dist/app/` and final installers go to `dist/installers/`.
|
|
87
87
|
|
|
88
88
|
**System Requirements:**
|
|
89
89
|
- `lotus.config.json` in the current directory
|
|
90
|
-
-
|
|
91
|
-
```bash
|
|
92
|
-
sudo dnf install rpm-build
|
|
93
|
-
```
|
|
94
|
-
- For DEB targets (Ubuntu/Debian):
|
|
95
|
-
```bash
|
|
96
|
-
sudo apt install dpkg-dev fakeroot
|
|
97
|
-
```
|
|
90
|
+
- Modern Node.js (v20+ with SEA support)
|
|
98
91
|
|
|
99
92
|
### `lotus clean`
|
|
100
93
|
|
|
@@ -159,19 +152,13 @@ After running `lotus build`, the `dist/` directory contains:
|
|
|
159
152
|
|
|
160
153
|
```
|
|
161
154
|
dist/
|
|
162
|
-
├── app/ # Staged application
|
|
163
|
-
│ ├──
|
|
164
|
-
│ ├──
|
|
165
|
-
│
|
|
166
|
-
│ └── LICENSE # License file
|
|
155
|
+
├── app/ # Staged application components
|
|
156
|
+
│ ├── my-app # Node SEA Single Executable User Binary
|
|
157
|
+
│ ├── lotus.linux-x64-gnu.node # Extracted native bindings
|
|
158
|
+
│ └── msgpackr-renderer.js
|
|
167
159
|
└── installers/
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
The generated wrapper script runs your app with Node.js:
|
|
172
|
-
```bash
|
|
173
|
-
#!/bin/sh
|
|
174
|
-
exec node "/usr/lib/my-app/resources/app/main.js" "$@"
|
|
160
|
+
├── my-app-1.0.0-x86_64.AppImage
|
|
161
|
+
└── my-app_1.0.0_amd64.deb
|
|
175
162
|
```
|
|
176
163
|
|
|
177
164
|
## Project Setup Example
|
|
@@ -208,8 +195,8 @@ my-lotus-app/
|
|
|
208
195
|
# Run with hot-reload
|
|
209
196
|
npx lotus dev main.js
|
|
210
197
|
|
|
211
|
-
# Build an
|
|
212
|
-
npx lotus build --
|
|
198
|
+
# Build an AppImage for Linux
|
|
199
|
+
npx lotus build --target appimage
|
|
213
200
|
|
|
214
201
|
# Clean build artifacts
|
|
215
202
|
npx lotus clean
|
|
@@ -218,23 +205,21 @@ npx lotus clean
|
|
|
218
205
|
### Install the Built Package
|
|
219
206
|
|
|
220
207
|
```bash
|
|
221
|
-
# RPM (Fedora/RHEL)
|
|
222
|
-
sudo dnf install ./dist/installers/my-app-1.0.0-1.x86_64.rpm
|
|
223
|
-
|
|
224
208
|
# DEB (Ubuntu/Debian)
|
|
225
|
-
sudo
|
|
209
|
+
sudo apt install ./dist/installers/my-app_1.0.0_amd64.deb
|
|
226
210
|
|
|
227
211
|
# Run it
|
|
228
212
|
my-app
|
|
213
|
+
|
|
214
|
+
# Or use the portable AppImage directly!
|
|
215
|
+
./dist/installers/my-app-1.0.0-x86_64.AppImage
|
|
229
216
|
```
|
|
230
217
|
|
|
231
218
|
## Architecture
|
|
232
219
|
|
|
233
220
|
```
|
|
234
221
|
@lotus-gui/dev
|
|
235
|
-
├── bin/lotus.js # CLI entry point (commander-based)
|
|
236
|
-
├── lib/templates/
|
|
237
|
-
│ └── spec.ejs # Custom RPM spec template
|
|
222
|
+
├── bin/lotus.js # CLI entry point (commander-based build pipeline)
|
|
238
223
|
├── index.js # Package entry (exports CLI path)
|
|
239
224
|
└── package.json
|
|
240
225
|
```
|
|
@@ -245,9 +230,9 @@ my-app
|
|
|
245
230
|
|---------|---------|
|
|
246
231
|
| `commander` | CLI argument parsing |
|
|
247
232
|
| `chokidar` | File watching for hot-reload |
|
|
248
|
-
| `
|
|
249
|
-
| `
|
|
250
|
-
| `
|
|
233
|
+
| `esbuild` | Code bundling and __dirname proxying |
|
|
234
|
+
| `@crabnebula/packager` | OS installation package generation (`.deb`, `.msi`, etc.) |
|
|
235
|
+
| `postject` | Injecting payloads into Node.js SEA binaries |
|
|
251
236
|
|
|
252
237
|
## License
|
|
253
238
|
|
package/bin/lotus.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { Command } = require('commander');
|
|
4
|
-
const { spawn } = require('child_process');
|
|
4
|
+
const { spawn, execSync } = require('child_process');
|
|
5
5
|
const chokidar = require('chokidar');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const prompts = require('prompts');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const { Jimp } = require('jimp');
|
|
9
11
|
|
|
10
12
|
const program = new Command();
|
|
11
13
|
|
|
@@ -70,7 +72,7 @@ program
|
|
|
70
72
|
.command('build')
|
|
71
73
|
.description('Build the application for production')
|
|
72
74
|
.option('--platform <platform>', 'Target platform (linux, win32)', process.platform)
|
|
73
|
-
.option('--target <target>', 'Target format (deb,
|
|
75
|
+
.option('--target <target>', 'Target format (deb, appimage, msi, nsis)', 'deb')
|
|
74
76
|
.action(async (cmdOptions) => {
|
|
75
77
|
const platform = cmdOptions.platform;
|
|
76
78
|
const target = cmdOptions.target;
|
|
@@ -85,173 +87,464 @@ program
|
|
|
85
87
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
86
88
|
const distDir = path.resolve('dist');
|
|
87
89
|
const appDir = path.join(distDir, 'app');
|
|
88
|
-
const resourcesDir = path.join(appDir, 'resources', 'app');
|
|
89
90
|
|
|
90
91
|
// Clean dist
|
|
91
92
|
if (fs.existsSync(distDir)) {
|
|
92
93
|
fs.rmSync(distDir, { recursive: true, force: true });
|
|
93
94
|
}
|
|
94
|
-
fs.mkdirSync(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
95
|
+
fs.mkdirSync(appDir, { recursive: true });
|
|
96
|
+
|
|
97
|
+
// Determine entry point: lotus.config.json > package.json > index.js
|
|
98
|
+
const appPackagePath = path.resolve('package.json');
|
|
99
|
+
let entryPoint = 'index.js';
|
|
100
|
+
if (fs.existsSync(appPackagePath)) {
|
|
101
|
+
const appPackageCtx = JSON.parse(fs.readFileSync(appPackagePath, 'utf8'));
|
|
102
|
+
entryPoint = config.main || appPackageCtx.main || 'index.js';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 1. Bundle JS with esbuild
|
|
106
|
+
console.log('Bundling application with esbuild...');
|
|
107
|
+
const bundlePath = path.join(appDir, 'bundle.js');
|
|
108
|
+
const shimPath = path.join(appDir, 'esbuild-shim.js');
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
// Write a shim to intercept __dirname logic for native modules
|
|
112
|
+
fs.writeFileSync(shimPath, `
|
|
113
|
+
import fs from 'fs';
|
|
114
|
+
import path from 'path';
|
|
115
|
+
const execDir = path.dirname(process.execPath);
|
|
116
|
+
const libDir = path.join('/usr/lib', path.basename(process.execPath));
|
|
117
|
+
const flatpakLibDir = path.join('/app/lib', path.basename(process.execPath));
|
|
118
|
+
let macroDir = execDir;
|
|
119
|
+
if (fs.existsSync(path.join(libDir, 'msgpackr-renderer.js'))) {
|
|
120
|
+
macroDir = libDir;
|
|
121
|
+
} else if (fs.existsSync(path.join(flatpakLibDir, 'msgpackr-renderer.js'))) {
|
|
122
|
+
macroDir = flatpakLibDir;
|
|
123
|
+
}
|
|
124
|
+
export const __dirname_macro = macroDir;
|
|
125
|
+
`.trim());
|
|
126
|
+
|
|
127
|
+
// We use esbuild to bundle, and specifically override __dirname so native modules
|
|
128
|
+
// can resolve their .node files sitting next to the final executable.
|
|
129
|
+
execSync(`npx esbuild "${entryPoint}" --bundle --platform=node --outfile="${bundlePath}" --external:*.node --inject:"${shimPath}" --define:__dirname=__dirname_macro`, { stdio: 'inherit' });
|
|
130
|
+
|
|
131
|
+
fs.unlinkSync(shimPath); // Clean up shim
|
|
132
|
+
|
|
133
|
+
// Node SEA fails to resolve built-in `require("./file.node")` relative to the VFS.
|
|
134
|
+
// It also forbids requiring external modules via the global `require` wrapper.
|
|
135
|
+
// We patch the bundle to force an absolute module path relative to our execPath,
|
|
136
|
+
// loaded using `module.createRequire` which escapes the SEA sandbox constraint.
|
|
137
|
+
// When installed via .deb or .rpm, resources are in /usr/lib/<appName>/ instead of /usr/bin/
|
|
138
|
+
let bundleContent = fs.readFileSync(bundlePath, 'utf8');
|
|
139
|
+
bundleContent = bundleContent.replace(/require\(['"]\.\/([^'"]+\.node)['"]\)/g, "require('module').createRequire(process.execPath)(require('path').join(__dirname_macro, '$1'))");
|
|
140
|
+
fs.writeFileSync(bundlePath, bundleContent);
|
|
141
|
+
} catch (err) {
|
|
142
|
+
console.error('esbuild failed');
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 2. Crawl and copy .node files
|
|
147
|
+
console.log('Extracting native .node modules...');
|
|
148
|
+
const findNodeFiles = (dir, fileList = [], visited = new Set()) => {
|
|
149
|
+
if (!fs.existsSync(dir)) return fileList;
|
|
150
|
+
const realDir = fs.realpathSync(dir);
|
|
151
|
+
if (visited.has(realDir)) return fileList;
|
|
152
|
+
visited.add(realDir);
|
|
153
|
+
|
|
154
|
+
let files = [];
|
|
155
|
+
try {
|
|
156
|
+
files = fs.readdirSync(dir);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
if (err.code === 'EACCES' || err.code === 'EPERM') return fileList;
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const file of files) {
|
|
163
|
+
const fullPath = path.join(dir, file);
|
|
164
|
+
if (file === '.git' || file === '.github' || file === '.flatpak-builder') continue;
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
168
|
+
findNodeFiles(fullPath, fileList, visited);
|
|
169
|
+
} else if (file.endsWith('.node')) {
|
|
170
|
+
fileList.push(fullPath);
|
|
171
|
+
}
|
|
172
|
+
} catch (err) {
|
|
173
|
+
if (err.code !== 'EACCES' && err.code !== 'EPERM') throw err;
|
|
174
|
+
}
|
|
108
175
|
}
|
|
176
|
+
return fileList;
|
|
109
177
|
};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
178
|
+
|
|
179
|
+
const nodeFiles = findNodeFiles(path.resolve('node_modules'));
|
|
180
|
+
for (const file of nodeFiles) {
|
|
181
|
+
const fileName = path.basename(file);
|
|
182
|
+
fs.copyFileSync(file, path.join(appDir, fileName));
|
|
183
|
+
console.log(`Copied ${fileName}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.log('Extracting msgpackr renderer script...');
|
|
187
|
+
let msgpackrRendererPath;
|
|
188
|
+
try {
|
|
189
|
+
msgpackrRendererPath = require.resolve('msgpackr/dist/index.min.js', { paths: [process.cwd()] });
|
|
190
|
+
} catch (e) {
|
|
191
|
+
try {
|
|
192
|
+
msgpackrRendererPath = path.join(path.dirname(require.resolve('msgpackr', { paths: [process.cwd()] })), 'index.min.js');
|
|
193
|
+
} catch (e2) {
|
|
194
|
+
console.warn('Could not locate msgpackr in node_modules! IPC may fail.');
|
|
122
195
|
}
|
|
196
|
+
}
|
|
197
|
+
if (msgpackrRendererPath && fs.existsSync(msgpackrRendererPath)) {
|
|
198
|
+
fs.copyFileSync(msgpackrRendererPath, path.join(appDir, 'msgpackr-renderer.js'));
|
|
199
|
+
console.log('Copied msgpackr-renderer.js');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 3. Node SEA Generation
|
|
203
|
+
console.log('Generating Node SEA...');
|
|
204
|
+
const binName = config.executableName || config.name.toLowerCase().replace(/ /g, '-');
|
|
205
|
+
const isWindows = platform === 'win32';
|
|
206
|
+
const binPath = isWindows ? path.join(appDir, `${binName}.exe`) : path.join(appDir, binName);
|
|
207
|
+
|
|
208
|
+
const seaConfigPath = path.join(appDir, 'sea-config.json');
|
|
209
|
+
const seaConfig = {
|
|
210
|
+
main: bundlePath,
|
|
211
|
+
output: path.join(appDir, 'sea-prep.blob'),
|
|
212
|
+
disableExperimentalSEAWarning: true
|
|
123
213
|
};
|
|
214
|
+
fs.writeFileSync(seaConfigPath, JSON.stringify(seaConfig, null, 2));
|
|
124
215
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
216
|
+
try {
|
|
217
|
+
execSync(`node --experimental-sea-config "${seaConfigPath}"`, { stdio: 'inherit' });
|
|
218
|
+
|
|
219
|
+
// Copy Node binary
|
|
220
|
+
fs.copyFileSync(process.execPath, binPath);
|
|
221
|
+
if (!isWindows) fs.chmodSync(binPath, 0o755);
|
|
222
|
+
|
|
223
|
+
// Inject blob
|
|
224
|
+
// The sentinel fuse is hardcoded per Node.js documentation for SEA
|
|
225
|
+
execSync(`npx postject "${binPath}" NODE_SEA_BLOB "${seaConfig.output}" --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2`, { env: { ...process.env }, stdio: 'inherit' });
|
|
226
|
+
|
|
227
|
+
// Cleanup temp SEA files
|
|
228
|
+
fs.unlinkSync(bundlePath);
|
|
229
|
+
fs.unlinkSync(seaConfig.output);
|
|
230
|
+
fs.unlinkSync(seaConfigPath);
|
|
231
|
+
} catch (err) {
|
|
232
|
+
console.error('SEA generation failed', err);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 4. CrabNebula Packaging
|
|
237
|
+
console.log('Packaging with CrabNebula...');
|
|
238
|
+
const packagerConfigPath = path.join(distDir, 'packager.json');
|
|
239
|
+
|
|
240
|
+
const packagerConfig = {
|
|
241
|
+
productName: config.name,
|
|
242
|
+
version: config.version,
|
|
243
|
+
description: config.description || config.name,
|
|
244
|
+
identifier: `com.lotus.${binName}`,
|
|
245
|
+
authors: [config.author || 'Lotus Dev'],
|
|
246
|
+
outDir: path.resolve(distDir, 'installers'),
|
|
247
|
+
// CrabNebula requires paths relative to the current directory where it runs
|
|
248
|
+
binariesDir: path.relative(distDir, appDir),
|
|
249
|
+
binaries: [
|
|
250
|
+
{
|
|
251
|
+
path: isWindows ? binName + '.exe' : binName,
|
|
252
|
+
main: true
|
|
253
|
+
}
|
|
254
|
+
],
|
|
255
|
+
// Instruct packager to copy our native `.node` modules together into the final installation directory
|
|
256
|
+
resources: [
|
|
257
|
+
path.join(path.relative(distDir, appDir), '*.node'),
|
|
258
|
+
path.join(path.relative(distDir, appDir), 'msgpackr-renderer.js')
|
|
259
|
+
],
|
|
260
|
+
deb: {
|
|
261
|
+
depends: []
|
|
262
|
+
}
|
|
132
263
|
};
|
|
133
|
-
copyNodeModules(path.join(process.cwd(), 'node_modules'), path.join(resourcesDir, 'node_modules'));
|
|
134
264
|
|
|
135
|
-
|
|
136
|
-
|
|
265
|
+
if (config.resources && Array.isArray(config.resources)) {
|
|
266
|
+
for (const res of config.resources) {
|
|
267
|
+
const resPath = path.resolve(process.cwd(), res);
|
|
268
|
+
if (fs.existsSync(resPath)) {
|
|
269
|
+
packagerConfig.resources.push(resPath);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
137
273
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (licenseSrc) {
|
|
141
|
-
fs.copyFileSync(licenseSrc, path.join(appDir, 'LICENSE'));
|
|
142
|
-
} else {
|
|
143
|
-
// Create a placeholder license if none exists
|
|
144
|
-
fs.writeFileSync(path.join(appDir, 'LICENSE'), `Copyright (c) ${new Date().getFullYear()} ${config.author || 'Lotus App Developer'}. All rights reserved.`);
|
|
274
|
+
if (config.icon) {
|
|
275
|
+
packagerConfig.icons = [path.resolve(config.icon)];
|
|
145
276
|
}
|
|
146
277
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
// Determine binary name
|
|
152
|
-
const binName = config.executableName || config.name.toLowerCase().replace(/ /g, '-');
|
|
153
|
-
const wmClass = config.build?.linux?.wmClass || binName;
|
|
154
|
-
|
|
155
|
-
// Use platform-appropriate arch and dependency names
|
|
156
|
-
const isRpm = target === 'rpm';
|
|
157
|
-
const arch = isRpm ? 'x86_64' : 'amd64';
|
|
158
|
-
const deps = isRpm
|
|
159
|
-
? ['nodejs', 'openssl-libs', 'gtk3', 'webkit2gtk4.0']
|
|
160
|
-
: ['nodejs', 'libssl-dev', 'libgtk-3-0', 'libwebkit2gtk-4.0-37'];
|
|
161
|
-
|
|
162
|
-
const options = {
|
|
163
|
-
src: appDir,
|
|
164
|
-
dest: path.join(distDir, 'installers'),
|
|
165
|
-
arch: arch,
|
|
166
|
-
name: binName,
|
|
167
|
-
productName: config.name,
|
|
168
|
-
genericName: config.name,
|
|
169
|
-
version: config.version,
|
|
170
|
-
description: config.description,
|
|
171
|
-
productDescription: config.description || config.name,
|
|
172
|
-
icon: config.icon ? path.resolve(config.icon) : undefined,
|
|
173
|
-
section: config.build?.linux?.section || 'utils',
|
|
174
|
-
categories: config.build?.linux?.categories || ['Utility'],
|
|
175
|
-
bin: binName,
|
|
176
|
-
depends: deps,
|
|
177
|
-
maintainer: config.author || 'Lotus App Developer',
|
|
178
|
-
homepage: config.homepage,
|
|
179
|
-
priority: 'optional',
|
|
180
|
-
license: config.license || 'Proprietary'
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
// Determine entry point: lotus.config.json > package.json > index.js
|
|
184
|
-
const appPackageCtx = JSON.parse(fs.readFileSync(path.join(resourcesDir, 'package.json'), 'utf8'));
|
|
185
|
-
const entryPoint = config.main || appPackageCtx.main || 'index.js';
|
|
186
|
-
|
|
187
|
-
// Create Wrapper Script at the ROOT of appDir (which will be installed to /usr/lib/APPNAME/)
|
|
188
|
-
// The script needs to execute node on the file in resources/app
|
|
189
|
-
const binScriptPath = path.join(appDir, binName);
|
|
190
|
-
|
|
191
|
-
// NOTE: electron-installer-debian installs 'src' content into '/usr/lib/<options.name>/'
|
|
192
|
-
// So our resources are at '/usr/lib/<options.name>/resources/app'
|
|
193
|
-
// And our binary (this script) is at '/usr/lib/<options.name>/<binName>'
|
|
194
|
-
|
|
195
|
-
const wrapperScript = `#!/bin/sh
|
|
196
|
-
exec node "/usr/lib/${options.name}/resources/app/${entryPoint}" "$@"
|
|
197
|
-
`;
|
|
198
|
-
fs.writeFileSync(binScriptPath, wrapperScript, { mode: 0o755 });
|
|
278
|
+
fs.writeFileSync(packagerConfigPath, JSON.stringify(packagerConfig, null, 2));
|
|
279
|
+
const setupAppDir = async (targetBuildSystem = target) => {
|
|
280
|
+
const appDirName = path.join(distDir, 'AppDir');
|
|
281
|
+
if (fs.existsSync(appDirName)) return appDirName;
|
|
199
282
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
283
|
+
const appId = config.appId || `org.lotus.${binName}`;
|
|
284
|
+
const desktopIconName = targetBuildSystem === 'flatpak' ? appId : binName;
|
|
285
|
+
|
|
286
|
+
const libDir = path.join(appDirName, 'usr', 'lib', binName);
|
|
287
|
+
fs.mkdirSync(path.join(appDirName, 'usr', 'bin'), { recursive: true });
|
|
288
|
+
fs.mkdirSync(libDir, { recursive: true });
|
|
289
|
+
|
|
290
|
+
// Copy the binary into usr/bin/
|
|
291
|
+
fs.copyFileSync(binPath, path.join(appDirName, 'usr', 'bin', binName));
|
|
292
|
+
if (!isWindows) fs.chmodSync(path.join(appDirName, 'usr', 'bin', binName), 0o755);
|
|
293
|
+
|
|
294
|
+
// Copy .node files and msgpackr into usr/lib/<binName>/ (FHS-compliant, matches __dirname_macro)
|
|
295
|
+
for (const file of nodeFiles) {
|
|
296
|
+
fs.copyFileSync(file, path.join(libDir, path.basename(file)));
|
|
297
|
+
}
|
|
298
|
+
const localMsgpackr = path.join(appDir, 'msgpackr-renderer.js');
|
|
299
|
+
if (fs.existsSync(localMsgpackr)) {
|
|
300
|
+
fs.copyFileSync(localMsgpackr, path.join(libDir, 'msgpackr-renderer.js'));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Copy extra resources into usr/lib/<binName>/
|
|
304
|
+
if (config.resources && Array.isArray(config.resources)) {
|
|
305
|
+
for (const res of config.resources) {
|
|
306
|
+
const resPath = path.resolve(process.cwd(), res);
|
|
307
|
+
if (fs.existsSync(resPath)) {
|
|
308
|
+
const destPath = path.join(libDir, path.basename(res));
|
|
309
|
+
if (fs.statSync(resPath).isDirectory()) {
|
|
310
|
+
fs.cpSync(resPath, destPath, { recursive: true });
|
|
311
|
+
} else {
|
|
312
|
+
fs.copyFileSync(resPath, destPath);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Create AppRun (for AppImage — launches via the wrapper)
|
|
319
|
+
const appRunPath = path.join(appDirName, 'AppRun');
|
|
320
|
+
fs.writeFileSync(appRunPath, `#!/bin/sh\nHERE="$(dirname "$(readlink -f "\${0}")")"\nexport LD_LIBRARY_PATH="\${HERE}/usr/lib:\${LD_LIBRARY_PATH}"\nexec "\${HERE}/usr/bin/${binName}" "$@"\n`);
|
|
321
|
+
fs.chmodSync(appRunPath, 0o755);
|
|
322
|
+
|
|
323
|
+
// Create .desktop
|
|
324
|
+
const desktopContent = `[Desktop Entry]\nName=${config.name}\nExec=${binName}\nIcon=${desktopIconName}\nType=Application\nCategories=Utility;\n`;
|
|
325
|
+
fs.writeFileSync(path.join(appDirName, `${binName}.desktop`), desktopContent);
|
|
326
|
+
|
|
327
|
+
// Create icon
|
|
328
|
+
if (config.icon && fs.existsSync(path.resolve(config.icon))) {
|
|
329
|
+
const iconPath = path.resolve(config.icon);
|
|
330
|
+
const ext = path.extname(iconPath) || '.png';
|
|
331
|
+
fs.copyFileSync(iconPath, path.join(appDirName, `${binName}${ext}`));
|
|
332
|
+
fs.copyFileSync(iconPath, path.join(appDirName, `.DirIcon`));
|
|
333
|
+
} else {
|
|
334
|
+
fs.writeFileSync(path.join(appDirName, `${binName}.png`), 'iVBO... (empty icon placeholder)');
|
|
335
|
+
fs.writeFileSync(path.join(appDirName, `.DirIcon`), 'empty');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Expose standard FreeDesktop paths for RPM and Flatpak
|
|
339
|
+
const appsDir = path.join(appDirName, 'usr', 'share', 'applications');
|
|
340
|
+
const iconsDir = path.join(appDirName, 'usr', 'share', 'icons', 'hicolor', '512x512', 'apps');
|
|
341
|
+
fs.mkdirSync(appsDir, { recursive: true });
|
|
342
|
+
fs.mkdirSync(iconsDir, { recursive: true });
|
|
343
|
+
fs.writeFileSync(path.join(appsDir, `${binName}.desktop`), desktopContent);
|
|
344
|
+
if (config.icon && fs.existsSync(path.resolve(config.icon))) {
|
|
345
|
+
const ext = path.extname(path.resolve(config.icon)) || '.png';
|
|
346
|
+
try {
|
|
347
|
+
const image = await Jimp.read(path.resolve(config.icon));
|
|
348
|
+
await image.resize({ w: 512, h: 512 });
|
|
349
|
+
await image.write(path.join(iconsDir, `${binName}${ext}`));
|
|
350
|
+
} catch (e) {
|
|
351
|
+
console.error("Failed to resize icon for Flatpak. Skipping...", e);
|
|
352
|
+
fs.copyFileSync(path.resolve(config.icon), path.join(iconsDir, `${binName}${ext}`));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return appDirName;
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
if (target === 'appimage') {
|
|
361
|
+
console.log('Building AppImage natively...');
|
|
362
|
+
|
|
363
|
+
const toolsDir = path.join(os.homedir(), '.lotus-gui', 'tools');
|
|
364
|
+
fs.mkdirSync(toolsDir, { recursive: true });
|
|
365
|
+
const appImageToolPath = path.join(toolsDir, 'appimagetool');
|
|
366
|
+
|
|
367
|
+
if (!fs.existsSync(appImageToolPath)) {
|
|
368
|
+
console.log('Downloading appimagetool... (this only happens once)');
|
|
369
|
+
try {
|
|
370
|
+
execSync(`wget -qO "${appImageToolPath}" https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage`, { stdio: 'inherit' });
|
|
371
|
+
fs.chmodSync(appImageToolPath, 0o755);
|
|
372
|
+
} catch (e) {
|
|
373
|
+
try {
|
|
374
|
+
execSync(`curl -sL -o "${appImageToolPath}" https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage`, { stdio: 'inherit' });
|
|
375
|
+
fs.chmodSync(appImageToolPath, 0o755);
|
|
376
|
+
} catch (e2) {
|
|
377
|
+
console.error('Failed to download appimagetool. Please install curl or wget.');
|
|
378
|
+
process.exit(1);
|
|
212
379
|
}
|
|
213
380
|
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const appDirName = await setupAppDir();
|
|
384
|
+
|
|
385
|
+
// Run appimagetool
|
|
386
|
+
console.log('Running appimagetool...');
|
|
387
|
+
const installersDir = path.join(distDir, 'installers');
|
|
388
|
+
fs.mkdirSync(installersDir, { recursive: true });
|
|
389
|
+
const outPath = path.join(installersDir, `${binName}-${config.version}-x86_64.AppImage`);
|
|
390
|
+
execSync(`"${appImageToolPath}" "${appDirName}" "${outPath}"`, { stdio: 'inherit', env: { ...process.env, ARCH: 'x86_64' } });
|
|
391
|
+
console.log(`Successfully created packages in ${installersDir}`);
|
|
214
392
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
393
|
+
} else {
|
|
394
|
+
// Provide format argument to crabnebula packager
|
|
395
|
+
// CrabNebula formats: deb, appimage, pacman (Linux); wix, nsis (Windows); app, dmg (Mac).
|
|
396
|
+
// Default to target if specified, else crabnebula chooses.
|
|
397
|
+
let formatArg = '';
|
|
398
|
+
if (target === 'deb' || target === 'appimage' || target === 'nsis' || target === 'wix' || target === 'msi' || target === 'exe' || target === 'pacman') {
|
|
399
|
+
const crabTarget = target === 'msi' ? 'wix' : target === 'exe' ? 'nsis' : target;
|
|
400
|
+
formatArg = `-f ${crabTarget}`;
|
|
401
|
+
} else if (target === 'rpm') {
|
|
402
|
+
console.log('Building manual RPM via rpmbuild...');
|
|
403
|
+
const installersDir = path.join(distDir, 'installers');
|
|
404
|
+
fs.mkdirSync(installersDir, { recursive: true });
|
|
405
|
+
|
|
406
|
+
const appDirName = await setupAppDir();
|
|
407
|
+
|
|
408
|
+
const rpmBuildDir = path.join(distDir, 'rpmbuild');
|
|
409
|
+
const rpmBuildRoot = path.join(rpmBuildDir, 'BUILDROOT');
|
|
410
|
+
fs.mkdirSync(path.join(rpmBuildDir, 'SPECS'), { recursive: true });
|
|
411
|
+
fs.mkdirSync(path.join(rpmBuildDir, 'RPMS'), { recursive: true });
|
|
412
|
+
|
|
413
|
+
let filesList = '/usr/bin/*\n';
|
|
414
|
+
filesList += `/usr/lib/${binName}/*\n`; // .node files live here
|
|
415
|
+
if (fs.existsSync(path.join(appDirName, 'usr', 'share', 'applications'))) {
|
|
416
|
+
filesList += '/usr/share/applications/*\n';
|
|
417
|
+
}
|
|
418
|
+
if (fs.existsSync(path.join(appDirName, 'usr', 'share', 'icons'))) {
|
|
419
|
+
filesList += '/usr/share/icons/*\n';
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const specContent = `
|
|
423
|
+
%define _unpackaged_files_terminate_build 0
|
|
424
|
+
%define __os_install_post %{nil}
|
|
425
|
+
%define debug_package %{nil}
|
|
426
|
+
%global __strip /bin/true
|
|
427
|
+
%global _build_id_links none
|
|
428
|
+
|
|
429
|
+
Name: ${config.name}
|
|
430
|
+
Version: ${config.version}
|
|
431
|
+
Release: 1%{?dist}
|
|
432
|
+
Summary: ${config.description || config.name}
|
|
433
|
+
License: ${config.license || 'Proprietary'}
|
|
434
|
+
${config.homepage ? `URL: ${config.homepage}` : ''}
|
|
435
|
+
BuildArch: x86_64
|
|
436
|
+
AutoReqProv: no
|
|
437
|
+
|
|
438
|
+
%description
|
|
439
|
+
${config.description || config.name}
|
|
440
|
+
|
|
441
|
+
%install
|
|
442
|
+
rm -rf %{buildroot}
|
|
443
|
+
mkdir -p %{buildroot}
|
|
444
|
+
cp -a ${path.join(distDir, 'AppDir')}/. %{buildroot}/
|
|
445
|
+
|
|
446
|
+
%files
|
|
447
|
+
${filesList}
|
|
448
|
+
|
|
449
|
+
%clean
|
|
450
|
+
rm -rf %{buildroot}
|
|
451
|
+
`;
|
|
452
|
+
const specPath = path.join(rpmBuildDir, 'SPECS', `${binName}.spec`);
|
|
453
|
+
fs.writeFileSync(specPath, specContent);
|
|
454
|
+
|
|
455
|
+
try {
|
|
456
|
+
execSync(`rpmbuild -bb --define "_topdir ${rpmBuildDir}" "${specPath}"`, { stdio: 'inherit' });
|
|
457
|
+
const rpmFile = fs.readdirSync(path.join(rpmBuildDir, 'RPMS', 'x86_64'))[0];
|
|
458
|
+
if (rpmFile) {
|
|
459
|
+
fs.copyFileSync(
|
|
460
|
+
path.join(rpmBuildDir, 'RPMS', 'x86_64', rpmFile),
|
|
461
|
+
path.join(installersDir, rpmFile)
|
|
462
|
+
);
|
|
463
|
+
console.log(`Successfully created packages in ${installersDir}`);
|
|
464
|
+
}
|
|
465
|
+
} catch (e) {
|
|
466
|
+
console.error('Failed to build RPM. Is rpmbuild installed?');
|
|
467
|
+
process.exit(1);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return; // exit the block instead of hitting crabnebula
|
|
471
|
+
} else if (target === 'flatpak') {
|
|
472
|
+
console.log('Building manual Flatpak via flatpak-builder...');
|
|
473
|
+
const installersDir = path.join(distDir, 'installers');
|
|
474
|
+
fs.mkdirSync(installersDir, { recursive: true });
|
|
475
|
+
|
|
476
|
+
const appDirName = await setupAppDir('flatpak');
|
|
477
|
+
|
|
478
|
+
const flatpakBuildDir = path.join(distDir, 'flatpakbuild');
|
|
479
|
+
const flatpakRepoDir = path.join(distDir, 'flatpakrepo');
|
|
480
|
+
fs.mkdirSync(flatpakBuildDir, { recursive: true });
|
|
481
|
+
|
|
482
|
+
const appId = config.appId || `org.lotus.${binName}`;
|
|
483
|
+
|
|
484
|
+
const manifest = {
|
|
485
|
+
"app-id": appId,
|
|
486
|
+
"runtime": "org.freedesktop.Platform",
|
|
487
|
+
"runtime-version": "24.08",
|
|
488
|
+
"sdk": "org.freedesktop.Sdk",
|
|
489
|
+
"command": binName,
|
|
490
|
+
"build-options": {
|
|
491
|
+
"strip": false,
|
|
492
|
+
"no-debuginfo": true
|
|
493
|
+
},
|
|
494
|
+
"finish-args": [
|
|
495
|
+
"--share=network",
|
|
496
|
+
"--share=ipc",
|
|
497
|
+
"--socket=x11",
|
|
498
|
+
"--socket=wayland",
|
|
499
|
+
"--device=dri",
|
|
500
|
+
"--filesystem=host"
|
|
501
|
+
],
|
|
502
|
+
"modules": [
|
|
503
|
+
{
|
|
504
|
+
"name": binName,
|
|
505
|
+
"buildsystem": "simple",
|
|
506
|
+
"build-commands": [
|
|
507
|
+
"cp -a AppDir/usr/* /app/",
|
|
508
|
+
`mv /app/share/applications/${binName}.desktop /app/share/applications/\${FLATPAK_ID}.desktop`,
|
|
509
|
+
`mv /app/share/icons/hicolor/512x512/apps/${binName}.png /app/share/icons/hicolor/512x512/apps/\${FLATPAK_ID}.png`
|
|
510
|
+
],
|
|
511
|
+
"sources": [
|
|
512
|
+
{
|
|
513
|
+
"type": "dir",
|
|
514
|
+
"path": appDirName,
|
|
515
|
+
"dest": "AppDir"
|
|
516
|
+
}
|
|
517
|
+
]
|
|
518
|
+
}
|
|
519
|
+
]
|
|
234
520
|
};
|
|
235
521
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
522
|
+
const manifestPath = path.join(flatpakBuildDir, 'manifest.json');
|
|
523
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
execSync(`flatpak-builder --repo=${flatpakRepoDir} --force-clean ${path.join(flatpakBuildDir, 'build')} ${manifestPath}`, { stdio: 'inherit' });
|
|
527
|
+
const flatpakFile = path.join(installersDir, `${binName}.flatpak`);
|
|
528
|
+
execSync(`flatpak build-bundle ${flatpakRepoDir} ${flatpakFile} ${appId}`, { stdio: 'inherit' });
|
|
529
|
+
console.log(`Successfully created packages in ${installersDir}`);
|
|
530
|
+
} catch (e) {
|
|
531
|
+
console.error('Failed to build Flatpak. Is flatpak-builder installed?');
|
|
532
|
+
process.exit(1);
|
|
533
|
+
}
|
|
239
534
|
|
|
240
|
-
|
|
241
|
-
} else { // Default to debian
|
|
242
|
-
console.log('Creating Debian package...');
|
|
243
|
-
const installer = require('electron-installer-debian');
|
|
244
|
-
await installer(options);
|
|
535
|
+
return; // exit the block instead of hitting crabnebula
|
|
245
536
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
537
|
+
|
|
538
|
+
// We pass the stringified JSON configuration directly to avoid file resolving bugs in CrabNebula CLI
|
|
539
|
+
const safeConfigJson = JSON.stringify(packagerConfig).replace(/'/g, "'\\''");
|
|
540
|
+
execSync(`npx @crabnebula/packager -c '${safeConfigJson}' ${formatArg}`, { stdio: 'inherit', cwd: distDir });
|
|
541
|
+
|
|
542
|
+
console.log(`Successfully created packages in ${path.join(distDir, 'installers')}`);
|
|
250
543
|
}
|
|
251
|
-
}
|
|
252
|
-
console.
|
|
544
|
+
} catch (err) {
|
|
545
|
+
console.error('CrabNebula Packaging failed', err);
|
|
546
|
+
process.exit(1);
|
|
253
547
|
}
|
|
254
|
-
|
|
255
548
|
});
|
|
256
549
|
|
|
257
550
|
program
|
|
@@ -287,7 +580,7 @@ program
|
|
|
287
580
|
const { overwrite } = await prompts({
|
|
288
581
|
type: 'confirm',
|
|
289
582
|
name: 'overwrite',
|
|
290
|
-
message: `Directory ${targetDir} already exists.
|
|
583
|
+
message: `Directory ${targetDir} already exists.Overwrite ? `,
|
|
291
584
|
initial: false
|
|
292
585
|
});
|
|
293
586
|
if (!overwrite) {
|
|
@@ -394,28 +687,28 @@ program
|
|
|
394
687
|
|
|
395
688
|
// main.js
|
|
396
689
|
const mainJs = `const { ServoWindow, app, ipcMain } = require('@lotus-gui/core');
|
|
397
|
-
const path = require('path');
|
|
398
|
-
|
|
399
|
-
app.warmup();
|
|
400
|
-
|
|
401
|
-
const win = new ServoWindow({
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
win.once('frame-ready', () => win.show());
|
|
413
|
-
|
|
414
|
-
ipcMain.on('hello', (data) => {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
});
|
|
418
|
-
`;
|
|
690
|
+
const path = require('path');
|
|
691
|
+
|
|
692
|
+
app.warmup();
|
|
693
|
+
|
|
694
|
+
const win = new ServoWindow({
|
|
695
|
+
id: 'main-window',
|
|
696
|
+
root: path.join(__dirname, 'ui'),
|
|
697
|
+
index: 'index.html',
|
|
698
|
+
width: 1024,
|
|
699
|
+
height: 768,
|
|
700
|
+
title: "${metadata.name}",
|
|
701
|
+
transparent: true,
|
|
702
|
+
visible: false
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
win.once('frame-ready', () => win.show());
|
|
706
|
+
|
|
707
|
+
ipcMain.on('hello', (data) => {
|
|
708
|
+
console.log('Received from renderer:', data);
|
|
709
|
+
ipcMain.send('reply', { message: 'Hello from Node.js!' });
|
|
710
|
+
});
|
|
711
|
+
`;
|
|
419
712
|
fs.writeFileSync(path.join(projectPath, 'main.js'), mainJs);
|
|
420
713
|
|
|
421
714
|
// UI Directory
|
|
@@ -423,58 +716,58 @@ ipcMain.on('hello', (data) => {
|
|
|
423
716
|
fs.mkdirSync(uiDir);
|
|
424
717
|
|
|
425
718
|
// ui/index.html
|
|
426
|
-
const indexHtml =
|
|
427
|
-
<html>
|
|
428
|
-
<head>
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
719
|
+
const indexHtml = `< !DOCTYPE html >
|
|
720
|
+
<html>
|
|
721
|
+
<head>
|
|
722
|
+
<title>${metadata.name}</title>
|
|
723
|
+
<style>
|
|
724
|
+
body {margin: 0; padding: 0; background: transparent; font-family: sans-serif; }
|
|
725
|
+
.app {
|
|
726
|
+
background: rgba(30, 30, 30, 0.95);
|
|
727
|
+
color: white;
|
|
728
|
+
height: 100vh;
|
|
729
|
+
display: flex;
|
|
730
|
+
flex-direction: column;
|
|
731
|
+
align-items: center;
|
|
732
|
+
justify-content: center;
|
|
733
|
+
border-radius: 8px; /* Optional rounded corners for the view */
|
|
441
734
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
735
|
+
button {
|
|
736
|
+
padding: 10px 20px;
|
|
737
|
+
font-size: 16px;
|
|
738
|
+
cursor: pointer;
|
|
739
|
+
background: #646cff;
|
|
740
|
+
color: white;
|
|
741
|
+
border: none;
|
|
742
|
+
border-radius: 4px;
|
|
450
743
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
</head>
|
|
454
|
-
<body>
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
744
|
+
button:hover {background: #535bf2; }
|
|
745
|
+
</style>
|
|
746
|
+
</head>
|
|
747
|
+
<body>
|
|
748
|
+
<div class="app">
|
|
749
|
+
<h1>Welcome to ${metadata.name} 🪷</h1>
|
|
750
|
+
<p>Powered by Lotus (Servo + Node.js)</p>
|
|
751
|
+
<button onclick="sendMessage()">Ping Node.js</button>
|
|
752
|
+
<p id="response"></p>
|
|
753
|
+
</div>
|
|
754
|
+
|
|
755
|
+
<script>
|
|
756
|
+
function sendMessage() {
|
|
757
|
+
window.lotus.send('hello', { timestamp: Date.now() });
|
|
465
758
|
}
|
|
466
759
|
|
|
467
760
|
window.lotus.on('reply', (data) => {
|
|
468
|
-
|
|
761
|
+
document.getElementById('response').innerText = data.message;
|
|
469
762
|
});
|
|
470
|
-
|
|
471
|
-
</body>
|
|
472
|
-
</html>`;
|
|
763
|
+
</script>
|
|
764
|
+
</body>
|
|
765
|
+
</html>`;
|
|
473
766
|
fs.writeFileSync(path.join(uiDir, 'index.html'), indexHtml);
|
|
474
767
|
|
|
475
|
-
console.log(`\n✅ Project initialized in ${projectPath}`);
|
|
476
|
-
console.log(`\nNext steps
|
|
477
|
-
console.log(` cd ${targetDir}`);
|
|
768
|
+
console.log(`\n✅ Project initialized in ${projectPath} `);
|
|
769
|
+
console.log(`\nNext steps: `);
|
|
770
|
+
console.log(` cd ${targetDir} `);
|
|
478
771
|
console.log(` npm install`);
|
|
479
772
|
console.log(` npx lotus dev\n`);
|
|
480
773
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lotus-gui/dev",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "The Lotus Toolkit",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,13 +13,12 @@
|
|
|
13
13
|
"author": "",
|
|
14
14
|
"license": "ISC",
|
|
15
15
|
"dependencies": {
|
|
16
|
+
"@crabnebula/packager": "^0.11.2",
|
|
16
17
|
"chokidar": "^3.5.3",
|
|
17
18
|
"commander": "^11.1.0",
|
|
18
|
-
"
|
|
19
|
+
"esbuild": "^0.27.3",
|
|
20
|
+
"jimp": "^1.6.0",
|
|
21
|
+
"postject": "^1.0.0-alpha.6",
|
|
19
22
|
"prompts": "^2.4.2"
|
|
20
|
-
},
|
|
21
|
-
"optionalDependencies": {
|
|
22
|
-
"electron-installer-debian": "^3.2.0",
|
|
23
|
-
"electron-installer-redhat": "^3.4.0"
|
|
24
23
|
}
|
|
25
|
-
}
|
|
24
|
+
}
|
package/sea-config.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"main":"test-sea-bundle.js","output":"test-sea.blob"}
|
package/shim.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { dirname } from "path"; export const __dirname_macro = dirname(process.execPath);
|