@lotus-gui/dev 0.1.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 ADDED
@@ -0,0 +1,218 @@
1
+ # @lotus-gui/dev
2
+
3
+ CLI toolkit for developing, building, and packaging Lotus applications into distributable installers.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @lotus-gui/dev
9
+ ```
10
+
11
+ This provides the `lotus` CLI command.
12
+
13
+ ## CLI Commands
14
+
15
+ ### `lotus dev [entry]`
16
+
17
+ Launch your app with hot-reloading. Watches for file changes and auto-restarts.
18
+
19
+ ```bash
20
+ lotus dev # Uses index.js
21
+ lotus dev main.js # Custom entry point
22
+ ```
23
+
24
+ **What it does:**
25
+ - Starts your app with Node.js
26
+ - Watches all files (ignoring `node_modules`, `dist`, dotfiles)
27
+ - Auto-restarts on any file change
28
+ - Kills the previous process cleanly
29
+
30
+ ### `lotus build`
31
+
32
+ Build your application into a distributable installer package.
33
+
34
+ ```bash
35
+ lotus build --platform linux --target deb
36
+ lotus build --platform linux --target rpm
37
+ ```
38
+
39
+ | Flag | Values | Default | Description |
40
+ |------|--------|---------|-------------|
41
+ | `--platform` | `linux`, `win32` | Current OS | Target platform |
42
+ | `--target` | `deb`, `rpm` | `deb` | Installer format (Linux only) |
43
+
44
+ **What it does:**
45
+ 1. Reads `lotus.config.json` from the current directory
46
+ 2. Copies your app files into a staging directory (`dist/app/resources/app/`)
47
+ 3. Copies `node_modules` (preserving package structure)
48
+ 4. Generates a wrapper shell script as the executable
49
+ 5. Packages everything into a `.deb` or `.rpm` installer
50
+ 6. Output goes to `dist/installers/`
51
+
52
+ **System Requirements:**
53
+ - `lotus.config.json` in the current directory
54
+ - For RPM targets (Fedora/RHEL):
55
+ ```bash
56
+ sudo dnf install rpm-build
57
+ ```
58
+ - For DEB targets (Ubuntu/Debian):
59
+ ```bash
60
+ sudo apt install dpkg-dev fakeroot
61
+ ```
62
+
63
+ ### `lotus clean`
64
+
65
+ Remove the `dist/` build artifacts directory.
66
+
67
+ ```bash
68
+ lotus clean
69
+ ```
70
+
71
+ ## `lotus.config.json`
72
+
73
+ The build command reads configuration from `lotus.config.json` in your project root. This file controls both the build output and the installer metadata.
74
+
75
+ ### Full Example
76
+
77
+ ```json
78
+ {
79
+ "name": "MyApp",
80
+ "version": "1.0.0",
81
+ "license": "MIT",
82
+ "description": "A desktop application built with Lotus",
83
+ "main": "main.js",
84
+ "executableName": "my-app",
85
+ "icon": "./assets/icon.png",
86
+ "author": "Your Name",
87
+ "homepage": "https://github.com/you/my-app",
88
+ "build": {
89
+ "linux": {
90
+ "wmClass": "my-app",
91
+ "section": "utils",
92
+ "categories": ["Utility", "Development"]
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ ### Field Reference
99
+
100
+ | Field | Required | Description |
101
+ |-------|----------|-------------|
102
+ | `name` | Yes | Application display name. Used as `productName` in installers. |
103
+ | `version` | Yes | Semver version string (e.g., `"1.0.0"`). |
104
+ | `license` | No | SPDX license identifier (e.g., `"MIT"`, `"ISC"`). Defaults to `"Proprietary"`. |
105
+ | `description` | No | Short description shown in package managers. |
106
+ | `main` | No | Entry point file. Determines what the installed app runs. Falls back to `package.json`'s `main`, then `index.js`. |
107
+ | `executableName` | No | Binary name (e.g., `my-app` → `/usr/bin/my-app`). Defaults to lowercase `name`. |
108
+ | `icon` | No | Path to application icon (relative to project root). |
109
+ | `author` | No | Maintainer name for package metadata. |
110
+ | `homepage` | No | Project URL for package metadata. |
111
+
112
+ ### Linux Build Options (`build.linux`)
113
+
114
+ | Field | Description |
115
+ |-------|-------------|
116
+ | `wmClass` | Window manager class identifier. Used for taskbar grouping. |
117
+ | `section` | Package section (default: `"utils"`). |
118
+ | `categories` | Desktop entry categories (e.g., `["Utility"]`). |
119
+
120
+ ## Build Output
121
+
122
+ After running `lotus build`, the `dist/` directory contains:
123
+
124
+ ```
125
+ dist/
126
+ ├── app/ # Staged application
127
+ │ ├── resources/app/ # Your app files + node_modules
128
+ │ ├── my-app # Wrapper shell script
129
+ │ ├── version # Version file
130
+ │ └── LICENSE # License file
131
+ └── installers/
132
+ └── my-app-1.0.0-1.x86_64.rpm # (or .deb)
133
+ ```
134
+
135
+ The generated wrapper script runs your app with Node.js:
136
+ ```bash
137
+ #!/bin/sh
138
+ exec node "/usr/lib/my-app/resources/app/main.js" "$@"
139
+ ```
140
+
141
+ ## Project Setup Example
142
+
143
+ A minimal Lotus project looks like this:
144
+
145
+ ```
146
+ my-lotus-app/
147
+ ├── lotus.config.json # Build configuration
148
+ ├── package.json # npm dependencies (@lotus-gui/core, @lotus-gui/dev)
149
+ ├── main.js # App entry point
150
+ └── ui/
151
+ └── index.html # Your UI
152
+ ```
153
+
154
+ **`package.json`:**
155
+ ```json
156
+ {
157
+ "name": "my-lotus-app",
158
+ "version": "1.0.0",
159
+ "main": "main.js",
160
+ "dependencies": {
161
+ "@lotus-gui/core": "^0.1.0"
162
+ },
163
+ "devDependencies": {
164
+ "@lotus-gui/dev": "^0.1.0"
165
+ }
166
+ }
167
+ ```
168
+
169
+ ### Development Workflow
170
+
171
+ ```bash
172
+ # Run with hot-reload
173
+ npx lotus dev main.js
174
+
175
+ # Build an RPM
176
+ npx lotus build --platform linux --target rpm
177
+
178
+ # Clean build artifacts
179
+ npx lotus clean
180
+ ```
181
+
182
+ ### Install the Built Package
183
+
184
+ ```bash
185
+ # RPM (Fedora/RHEL)
186
+ sudo dnf install ./dist/installers/my-app-1.0.0-1.x86_64.rpm
187
+
188
+ # DEB (Ubuntu/Debian)
189
+ sudo dpkg -i ./dist/installers/my-app_1.0.0_amd64.deb
190
+
191
+ # Run it
192
+ my-app
193
+ ```
194
+
195
+ ## Architecture
196
+
197
+ ```
198
+ @lotus-gui/dev
199
+ ├── bin/lotus.js # CLI entry point (commander-based)
200
+ ├── lib/templates/
201
+ │ └── spec.ejs # Custom RPM spec template
202
+ ├── index.js # Package entry (exports CLI path)
203
+ └── package.json
204
+ ```
205
+
206
+ ### Dependencies
207
+
208
+ | Package | Purpose |
209
+ |---------|---------|
210
+ | `commander` | CLI argument parsing |
211
+ | `chokidar` | File watching for hot-reload |
212
+ | `electron-installer-debian` | `.deb` package generation |
213
+ | `electron-installer-redhat` | `.rpm` package generation |
214
+ | `electron-winstaller` | Windows installer generation (planned) |
215
+
216
+ ## License
217
+
218
+ MIT
package/bin/lotus.js ADDED
@@ -0,0 +1,256 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander');
4
+ const { spawn } = require('child_process');
5
+ const chokidar = require('chokidar');
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+
9
+ const program = new Command();
10
+
11
+ program
12
+ .name('lotus')
13
+ .description('Lotus Project CLI')
14
+ .version('0.1.0');
15
+
16
+ program
17
+ .command('dev [entry]')
18
+ .description('Launch the Lotus runner with hot-reloading')
19
+ .action((entry = 'index.js') => {
20
+ console.log(`Starting Lotus dev server...`);
21
+
22
+ let appProcess = null;
23
+
24
+ const startApp = () => {
25
+ if (appProcess) {
26
+ appProcess.kill();
27
+ }
28
+
29
+ console.log(`Launching ${entry}...`);
30
+ appProcess = spawn('node', [entry], {
31
+ stdio: 'inherit',
32
+ env: { ...process.env, LOTUS_DEV: 'true' }
33
+ });
34
+
35
+ appProcess.on('close', (code) => {
36
+ if (code !== 0 && code !== null) {
37
+ console.log(`Lotus app exited with code ${code}`);
38
+ }
39
+ });
40
+ };
41
+
42
+ const watcher = chokidar.watch('.', {
43
+ ignored: /(^|[\/\\])\..|node_modules|dist/,
44
+ persistent: true
45
+ });
46
+
47
+ watcher.on('change', (path) => {
48
+ console.log(`File ${path} has been changed`);
49
+ startApp();
50
+ });
51
+
52
+ startApp();
53
+ });
54
+
55
+ program
56
+ .command('clean')
57
+ .description('Remove build artifacts (dist/ directory)')
58
+ .action(() => {
59
+ const distDir = path.resolve('dist');
60
+ if (fs.existsSync(distDir)) {
61
+ fs.rmSync(distDir, { recursive: true, force: true });
62
+ console.log('Cleaned dist/ directory.');
63
+ } else {
64
+ console.log('Nothing to clean.');
65
+ }
66
+ });
67
+
68
+ program
69
+ .command('build')
70
+ .description('Build the application for production')
71
+ .option('--platform <platform>', 'Target platform (linux, win32)', process.platform)
72
+ .option('--target <target>', 'Target format (deb, rpm)', 'deb')
73
+ .action(async (cmdOptions) => {
74
+ const platform = cmdOptions.platform;
75
+ const target = cmdOptions.target;
76
+ console.log(`Building for ${platform} (${target})...`);
77
+
78
+ const configPath = path.resolve('lotus.config.json');
79
+ if (!fs.existsSync(configPath)) {
80
+ console.error('Error: lotus.config.json not found.');
81
+ process.exit(1);
82
+ }
83
+
84
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
85
+ const distDir = path.resolve('dist');
86
+ const appDir = path.join(distDir, 'app');
87
+ const resourcesDir = path.join(appDir, 'resources', 'app');
88
+
89
+ // Clean dist
90
+ if (fs.existsSync(distDir)) {
91
+ fs.rmSync(distDir, { recursive: true, force: true });
92
+ }
93
+ fs.mkdirSync(resourcesDir, { recursive: true });
94
+
95
+ console.log('Copying application files...');
96
+
97
+ // For copying app's own files — skip build artifacts, dev dirs
98
+ const copyAppFiles = (src, dest) => {
99
+ if (fs.statSync(src).isDirectory()) {
100
+ if (!fs.existsSync(dest)) fs.mkdirSync(dest);
101
+ fs.readdirSync(src).forEach(child => {
102
+ if (child === 'dist' || child === '.git' || child === '.github' || child === 'node_modules' || child === 'packages' || child === '.DS_Store' || child === 'target' || child === 'servo') return;
103
+ copyAppFiles(path.join(src, child), path.join(dest, child));
104
+ });
105
+ } else {
106
+ fs.copyFileSync(src, dest);
107
+ }
108
+ };
109
+ copyAppFiles(process.cwd(), resourcesDir);
110
+
111
+ // For copying node_modules — don't skip dist, only skip .git
112
+ const copyModuleFiles = (src, dest) => {
113
+ if (fs.statSync(src).isDirectory()) {
114
+ if (!fs.existsSync(dest)) fs.mkdirSync(dest);
115
+ fs.readdirSync(src).forEach(child => {
116
+ if (child === '.git') return;
117
+ copyModuleFiles(path.join(src, child), path.join(dest, child));
118
+ });
119
+ } else {
120
+ fs.copyFileSync(src, dest);
121
+ }
122
+ };
123
+
124
+ const copyNodeModules = (src, dest) => {
125
+ if (!fs.existsSync(src)) return;
126
+ if (!fs.existsSync(dest)) fs.mkdirSync(dest);
127
+ fs.readdirSync(src).forEach(child => {
128
+ if (child.startsWith('.')) return;
129
+ copyModuleFiles(path.join(src, child), path.join(dest, child));
130
+ });
131
+ };
132
+ copyNodeModules(path.join(process.cwd(), 'node_modules'), path.join(resourcesDir, 'node_modules'));
133
+
134
+ // Create version file for electron-installer-debian in the ROOT of the appDir (it expects it there)
135
+ fs.writeFileSync(path.join(appDir, 'version'), config.version || '0.1.0');
136
+
137
+ // Handle LICENSE file (required by electron-installer-debian)
138
+ const licenseSrc = ['LICENSE', 'LICENSE.md', 'LICENSE.txt'].find(f => fs.existsSync(f));
139
+ if (licenseSrc) {
140
+ fs.copyFileSync(licenseSrc, path.join(appDir, 'LICENSE'));
141
+ } else {
142
+ // Create a placeholder license if none exists
143
+ fs.writeFileSync(path.join(appDir, 'LICENSE'), `Copyright (c) ${new Date().getFullYear()} ${config.author || 'Lotus App Developer'}. All rights reserved.`);
144
+ }
145
+
146
+ // Verify @lotus-gui/core binary
147
+ // ...
148
+
149
+ if (platform === 'linux') {
150
+ // Determine binary name
151
+ const binName = config.executableName || config.name.toLowerCase().replace(/ /g, '-');
152
+ const wmClass = config.build?.linux?.wmClass || binName;
153
+
154
+ // Use platform-appropriate arch and dependency names
155
+ const isRpm = target === 'rpm';
156
+ const arch = isRpm ? 'x86_64' : 'amd64';
157
+ const deps = isRpm
158
+ ? ['nodejs', 'openssl-libs', 'gtk3', 'webkit2gtk4.0']
159
+ : ['nodejs', 'libssl-dev', 'libgtk-3-0', 'libwebkit2gtk-4.0-37'];
160
+
161
+ const options = {
162
+ src: appDir,
163
+ dest: path.join(distDir, 'installers'),
164
+ arch: arch,
165
+ name: binName,
166
+ productName: config.name,
167
+ genericName: config.name,
168
+ version: config.version,
169
+ description: config.description,
170
+ productDescription: config.description || config.name,
171
+ icon: config.icon ? path.resolve(config.icon) : undefined,
172
+ section: config.build?.linux?.section || 'utils',
173
+ categories: config.build?.linux?.categories || ['Utility'],
174
+ bin: binName,
175
+ depends: deps,
176
+ maintainer: config.author || 'Lotus App Developer',
177
+ homepage: config.homepage,
178
+ priority: 'optional',
179
+ license: config.license || 'Proprietary'
180
+ };
181
+
182
+ // Determine entry point: lotus.config.json > package.json > index.js
183
+ const appPackageCtx = JSON.parse(fs.readFileSync(path.join(resourcesDir, 'package.json'), 'utf8'));
184
+ const entryPoint = config.main || appPackageCtx.main || 'index.js';
185
+
186
+ // Create Wrapper Script at the ROOT of appDir (which will be installed to /usr/lib/APPNAME/)
187
+ // The script needs to execute node on the file in resources/app
188
+ const binScriptPath = path.join(appDir, binName);
189
+
190
+ // NOTE: electron-installer-debian installs 'src' content into '/usr/lib/<options.name>/'
191
+ // So our resources are at '/usr/lib/<options.name>/resources/app'
192
+ // And our binary (this script) is at '/usr/lib/<options.name>/<binName>'
193
+
194
+ const wrapperScript = `#!/bin/sh
195
+ exec node "/usr/lib/${options.name}/resources/app/${entryPoint}" "$@"
196
+ `;
197
+ fs.writeFileSync(binScriptPath, wrapperScript, { mode: 0o755 });
198
+
199
+ try {
200
+ if (target === 'rpm') {
201
+ console.log('Creating RPM package...');
202
+ const { Installer } = require('electron-installer-redhat');
203
+ const common = require('electron-installer-common');
204
+
205
+ class RPMInstaller extends Installer {
206
+ async createSpec() {
207
+ // Point to our custom template in packages/lotus-dev/lib/templates/spec.ejs
208
+ const templatePath = path.resolve(__dirname, '../lib/templates/spec.ejs');
209
+ this.options.logger(`Creating spec file at ${this.specPath} using custom template`);
210
+ return common.wrapError('creating spec file', async () => this.createTemplatedFile(templatePath, this.specPath));
211
+ }
212
+ }
213
+
214
+ // Replicate module.exports logic from electron-installer-redhat
215
+ const buildRpm = async (data) => {
216
+ // Mock logger
217
+ data.logger = data.logger || ((msg) => console.log(msg));
218
+
219
+ // Mock rename function (default from electron-installer-redhat)
220
+ data.rename = data.rename || function (dest, src) {
221
+ return path.join(dest, '<%= name %>-<%= version %>-<%= revision %>.<%= arch %>.rpm');
222
+ };
223
+
224
+ const installer = new RPMInstaller(data);
225
+ await installer.generateDefaults();
226
+ await installer.generateOptions();
227
+ await installer.generateScripts();
228
+ await installer.createStagingDir();
229
+ await installer.createContents();
230
+ await installer.createPackage();
231
+ await installer.movePackage();
232
+ return installer.options;
233
+ };
234
+
235
+ // RPM specific adjustments
236
+ options.requires = options.depends; // RPM uses 'requires', not 'depends'
237
+ delete options.depends;
238
+
239
+ await buildRpm(options);
240
+ } else { // Default to debian
241
+ console.log('Creating Debian package...');
242
+ const installer = require('electron-installer-debian');
243
+ await installer(options);
244
+ }
245
+ console.log(`Successfully created package at ${options.dest}`);
246
+ } catch (err) {
247
+ console.error(err, err.stack);
248
+ process.exit(1);
249
+ }
250
+ } else {
251
+ console.log('Packager for this platform not fully implemented yet.');
252
+ }
253
+
254
+ });
255
+
256
+ program.parse();
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ cli: require.resolve('./bin/lotus.js')
3
+ };
@@ -0,0 +1,65 @@
1
+ %define _binary_payload w<%= compressionLevel %>.xzdio
2
+
3
+ %if "%{_host_cpu}" != "%{_target_cpu}"
4
+ %global __strip /bin/true
5
+ %endif
6
+
7
+ Name: <%= name %>
8
+ Version: <%= version %>
9
+ Release: <%= revision %>%{?dist}
10
+ <% if (description) { print('Summary: ' + description + '\n') } %>
11
+ <% if (license) { print('License: ' + license + '\n') } %>
12
+ <% if (homepage) { print('URL: ' + homepage + '\n') } %>
13
+ <% if (license || homepage) { print('\n') } %>
14
+ Requires: <%= requires.join(', ') %>
15
+ AutoReqProv: no
16
+
17
+ <% if (productDescription) { %>
18
+ %description
19
+ <% print(productDescription) %>
20
+ <% print('\n\n\n') %>
21
+ <% } %>
22
+
23
+ %install
24
+ mkdir -p %{buildroot}/usr/
25
+ if [ -d "usr" ]; then
26
+ cp <%= process.platform === 'darwin' ? '-R' : '-r' %> usr/* %{buildroot}/usr/
27
+ else
28
+ cp <%= process.platform === 'darwin' ? '-R' : '-r' %> ../usr/* %{buildroot}/usr/
29
+ fi
30
+
31
+ %files
32
+ /usr/bin/<%= name %>
33
+ /usr/lib/<%= name %>/
34
+ /usr/share/applications/<%= name %>.desktop
35
+ /usr/share/doc/<%= name %>/
36
+ <% if (_.isObject(icon)) { %>
37
+ <% _.forEach(icon, function (path, resolution) { %>
38
+ /usr/share/icons/hicolor/<%= resolution %>/apps/<%= name %><%= resolution === 'symbolic' ? '-symbolic' : '' %>.<%= ['scalable', 'symbolic'].includes(resolution) ? 'svg' : 'png' %>
39
+ <% }) %>
40
+ <% } else { %>
41
+ /usr/share/pixmaps/<%= name %>.png
42
+ <% } %>
43
+
44
+ <% if (pre) { %>
45
+ %pre
46
+ <% print(pre) %>
47
+ <% if (preun || post || postun) { print('\n\n\n') } %>
48
+ <% } %>
49
+
50
+ <% if (preun) { %>
51
+ %preun
52
+ <% print(preun) %>
53
+ <% if (post || postun) { print('\n\n\n') } %>
54
+ <% } %>
55
+
56
+ <% if (post) { %>
57
+ %post
58
+ <% print(post) %>
59
+ <% if (postun) { print('\n\n\n') } %>
60
+ <% } %>
61
+
62
+ <% if (postun) { %>
63
+ %postun
64
+ <% print(postun) %>
65
+ <% } %>
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@lotus-gui/dev",
3
+ "version": "0.1.0",
4
+ "description": "The Lotus Toolkit",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "lotus": "./bin/lotus.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "keywords": [],
13
+ "author": "",
14
+ "license": "ISC",
15
+ "dependencies": {
16
+ "chokidar": "^3.5.3",
17
+ "commander": "^11.1.0",
18
+ "electron-winstaller": "^5.4.0"
19
+ },
20
+ "optionalDependencies": {
21
+ "electron-installer-debian": "^3.2.0",
22
+ "electron-installer-redhat": "^3.4.0"
23
+ }
24
+ }