omarchy-ui 0.0.7-x86_64-linux → 0.0.9-x86_64-linux
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.
- checksums.yaml +4 -4
- data/README.md +78 -106
- data/lib/omarchy_ui/cli.rb +73 -20
- data/lib/omarchy_ui/generator.rb +130 -3
- data/lib/omarchy_ui/project_config.rb +208 -0
- data/lib/omarchy_ui/qml_compiler.rb +386 -0
- data/lib/omarchy_ui/runtime.rb +10 -3
- data/lib/omarchy_ui.rb +3 -1
- data/vendor/runtime/x86_64-linux/RUNTIME_PROVENANCE.md +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 82c12f3f51c0d9242be7386a4994c4f3fe8b333d04489e4055b671054cb2f0e2
|
|
4
|
+
data.tar.gz: e693d5b68a5ad055b40fbb910b4bb6ef41b6de7405027396305f256ad744875d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 807cb5e30c33130681351065bc7fce902a7c4c125f1c6b47e8827db0ec6b49695745ab60d61da9475153f8abf1422940df280154af797ede39ef218a9097c449
|
|
7
|
+
data.tar.gz: c2f3532f53a5725e7c6e7cd9de667ebbb2dddd086204af8172f8b262c010e4fef2470b73716a7496c9859d396f43b22814e03926949944e2e1c080e1e64ca5a2
|
data/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
|
-
<img alt="Omarchy UI 0.0.
|
|
13
|
+
<img alt="Omarchy UI 0.0.9" src="https://img.shields.io/badge/Omarchy_UI-0.0.9-b7ff5a?style=flat-square&labelColor=111711">
|
|
14
14
|
<img alt="Ruby 3.1 or newer" src="https://img.shields.io/badge/Ruby-3.1%2B-cc342d?style=flat-square&logo=ruby&logoColor=white">
|
|
15
15
|
<img alt="Zui 0.0.10" src="https://img.shields.io/badge/Zui-0.0.10-7ee14b?style=flat-square&labelColor=111711">
|
|
16
16
|
<img alt="241 components" src="https://img.shields.io/badge/components-241-75d943?style=flat-square&labelColor=111711">
|
|
@@ -46,27 +46,29 @@ and the Omarchy adapter.
|
|
|
46
46
|
</table>
|
|
47
47
|
|
|
48
48
|
> [!IMPORTANT]
|
|
49
|
-
> Omarchy UI currently targets **Omarchy on x86-64 Linux
|
|
50
|
-
>
|
|
49
|
+
> Omarchy UI currently targets **Omarchy on x86-64 Linux**. Reusable Zui application modules can
|
|
50
|
+
> also run through Zui's native host on Linux, macOS, and Windows.
|
|
51
51
|
|
|
52
52
|
## Choose a workflow
|
|
53
53
|
|
|
54
54
|
| Build | Use it for | Primary surface | Delivery |
|
|
55
55
|
| --- | --- | --- | --- |
|
|
56
|
-
| Omarchy application | Full desktop tools and standalone interfaces | `app :main` | `omarchy_ui run` or `
|
|
57
|
-
| Omarchy plugin | Bar widgets, panels, and shell-integrated tools | `bar_widget` and `panel` | `omarchy_ui
|
|
56
|
+
| Omarchy application | Full desktop tools and standalone interfaces | `app :main` | `omarchy_ui new NAME`, then `run` or `bundle` |
|
|
57
|
+
| Omarchy plugin | Bar widgets, panels, and shell-integrated tools | `bar_widget` and `panel` | `omarchy_ui new NAME --plugin`, then `push` or `bundle` |
|
|
58
58
|
| Portable Zui application | The same application UI outside Omarchy | `app :main` | The platform-neutral [Zui toolchain](https://github.com/AdamMusa/zui#developer-workflow) |
|
|
59
59
|
|
|
60
60
|
## Requirements
|
|
61
61
|
|
|
62
|
-
- Omarchy on x86-64 Linux
|
|
63
|
-
- Ruby 3.1 or newer
|
|
64
|
-
-
|
|
62
|
+
- Omarchy on x86-64 Linux
|
|
63
|
+
- Ruby 3.1 or newer for installation and development
|
|
64
|
+
- Qt 6 development tools, CMake, Ninja, and a C++ compiler for `bundle`, `validate`, and `push`
|
|
65
|
+
- Optional Qt modules for components that use features such as Quick 3D or Multimedia
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
Omarchy UI turns Ruby and assets into an Omarchy-compatible application or plugin. It owns the
|
|
68
|
+
metadata, entry points, adapter, validation, tree-shaken and AOT-compiled Zui QML, and embedded mruby
|
|
69
|
+
runtime. Omarchy UI invokes the build tools; projects do not contain CMake files or distribution
|
|
70
|
+
scripts. The finished package does not require Ruby or framework gems. Components report a clear
|
|
71
|
+
error when a required Qt module or resource is unavailable.
|
|
70
72
|
|
|
71
73
|
## Quick start
|
|
72
74
|
|
|
@@ -78,20 +80,36 @@ gem install omarchy-ui
|
|
|
78
80
|
|
|
79
81
|
The gem installs the compatible Zui version as a dependency.
|
|
80
82
|
|
|
81
|
-
### 2. Generate
|
|
83
|
+
### 2. Generate a project
|
|
82
84
|
|
|
83
85
|
```bash
|
|
84
86
|
omarchy_ui new Counter
|
|
85
87
|
cd counter
|
|
86
88
|
```
|
|
87
89
|
|
|
90
|
+
For an integrated Omarchy plugin instead:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
omarchy_ui new "System Status" --plugin
|
|
94
|
+
cd system-status
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Both commands create a ready-to-run Ruby project. Developers write the interface and behavior;
|
|
98
|
+
Omarchy UI generates the Omarchy package without application-owned QML, shell entry points, or
|
|
99
|
+
handwritten manifests.
|
|
100
|
+
|
|
101
|
+
Each project includes a Ruby-native `config.rb` with its type, identity, version, entry point, and
|
|
102
|
+
plugin metadata. `bundle`, `validate`, and `push` use this single configuration.
|
|
103
|
+
|
|
88
104
|
The generator creates a small, host-independent project:
|
|
89
105
|
|
|
90
106
|
```text
|
|
91
107
|
counter/
|
|
108
|
+
├── LICENSE
|
|
92
109
|
├── app.rb # reusable Zui application
|
|
93
110
|
├── components/
|
|
94
111
|
│ └── welcome.rb # application-owned Ruby UI module
|
|
112
|
+
├── config.rb # project identity and build settings
|
|
95
113
|
├── main.rb # Omarchy launcher
|
|
96
114
|
└── README.md
|
|
97
115
|
```
|
|
@@ -102,8 +120,8 @@ counter/
|
|
|
102
120
|
omarchy_ui run main.rb
|
|
103
121
|
```
|
|
104
122
|
|
|
105
|
-
`run` bundles the project's Ruby entry point, asks Zui to tree-shake the QML runtime, prepares the
|
|
106
|
-
|
|
123
|
+
`run` bundles the project's Ruby entry point, asks Zui to tree-shake the QML runtime, prepares the
|
|
124
|
+
Omarchy host, and launches it through Quickshell.
|
|
107
125
|
|
|
108
126
|
## Application model
|
|
109
127
|
|
|
@@ -228,48 +246,17 @@ QML is not required.
|
|
|
228
246
|
|
|
229
247
|
## Omarchy plugins
|
|
230
248
|
|
|
231
|
-
|
|
232
|
-
at its root. Omarchy UI adds the generated service, panel, bar-widget, framework, and runtime files
|
|
233
|
-
while staging the plugin; do not commit generated copies to the application source.
|
|
249
|
+
Generate a plugin project with one command:
|
|
234
250
|
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
├── main.rb
|
|
239
|
-
└── assets/
|
|
251
|
+
```bash
|
|
252
|
+
omarchy_ui new "System Status" --plugin
|
|
253
|
+
cd system-status
|
|
240
254
|
```
|
|
241
255
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
This complete manifest declares the adapter surfaces used by the Ruby definition below:
|
|
245
|
-
|
|
246
|
-
```json
|
|
247
|
-
{
|
|
248
|
-
"schemaVersion": 1,
|
|
249
|
-
"id": "example.system-status",
|
|
250
|
-
"name": "System Status",
|
|
251
|
-
"version": "0.1.0",
|
|
252
|
-
"author": "Example Author",
|
|
253
|
-
"license": "MIT",
|
|
254
|
-
"description": "A Ruby-powered status panel for Omarchy.",
|
|
255
|
-
"kinds": ["service", "bar-widget", "panel"],
|
|
256
|
-
"keepLoaded": true,
|
|
257
|
-
"entryPoints": {
|
|
258
|
-
"service": "Service.qml",
|
|
259
|
-
"barWidget": "BarWidget.qml",
|
|
260
|
-
"panel": "Panel.qml"
|
|
261
|
-
},
|
|
262
|
-
"barWidget": {
|
|
263
|
-
"displayName": "System Status",
|
|
264
|
-
"description": "Open the current system status.",
|
|
265
|
-
"category": "System",
|
|
266
|
-
"allowMultiple": false,
|
|
267
|
-
"defaultSection": "right"
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
```
|
|
256
|
+
Developers work in Ruby and add their own assets. Omarchy UI supplies the plugin metadata, shell
|
|
257
|
+
surfaces, native host, tree-shaken Zui package, validation, and installation lifecycle.
|
|
271
258
|
|
|
272
|
-
|
|
259
|
+
The Ruby entry point uses ordinary Zui components:
|
|
273
260
|
|
|
274
261
|
```ruby
|
|
275
262
|
# main.rb
|
|
@@ -294,36 +281,26 @@ OmarchyUI.plugin do
|
|
|
294
281
|
end
|
|
295
282
|
```
|
|
296
283
|
|
|
297
|
-
`bar_widget`
|
|
298
|
-
|
|
284
|
+
`bar_widget` and `panel :status` are the developer-facing surface declarations. Omarchy UI maps them
|
|
285
|
+
to the required shell entry points when it builds or stages the plugin.
|
|
299
286
|
|
|
300
287
|
### Validate and install
|
|
301
288
|
|
|
302
289
|
```bash
|
|
303
|
-
omarchy_ui validate
|
|
304
|
-
omarchy_ui push
|
|
290
|
+
omarchy_ui validate
|
|
291
|
+
omarchy_ui push
|
|
305
292
|
```
|
|
306
293
|
|
|
307
|
-
`validate` checks
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
1. stages and validates the complete plugin;
|
|
311
|
-
2. backs up an existing installation when present;
|
|
312
|
-
3. installs it atomically under `~/.config/omarchy/plugins/<plugin-id>`;
|
|
313
|
-
4. enables the plugin; and
|
|
314
|
-
5. restarts the Omarchy shell.
|
|
294
|
+
`validate` generates and checks the plugin without modifying the Ruby project. `push` builds,
|
|
295
|
+
validates, installs, enables, and reloads it for development.
|
|
315
296
|
|
|
316
297
|
During development, suppress either lifecycle action when needed:
|
|
317
298
|
|
|
318
299
|
```bash
|
|
319
|
-
omarchy_ui push
|
|
320
|
-
omarchy_ui push
|
|
300
|
+
omarchy_ui push --no-enable
|
|
301
|
+
omarchy_ui push --no-restart
|
|
321
302
|
```
|
|
322
303
|
|
|
323
|
-
> [!WARNING]
|
|
324
|
-
> Omarchy plugins execute as unsandboxed user code inside the shell environment. Review the Ruby,
|
|
325
|
-
> assets, manifest, generated package, and provenance before installing a third-party plugin.
|
|
326
|
-
|
|
327
304
|
## Command-line reference
|
|
328
305
|
|
|
329
306
|
```text
|
|
@@ -332,10 +309,11 @@ omarchy_ui <command> [arguments]
|
|
|
332
309
|
|
|
333
310
|
| Command | Arguments | Result |
|
|
334
311
|
| --- | --- | --- |
|
|
335
|
-
| `omarchy_ui new NAME` | One
|
|
312
|
+
| `omarchy_ui new NAME` | One project name | Creates an Omarchy application project |
|
|
313
|
+
| `omarchy_ui new NAME --plugin` | One project name and `--plugin` | Creates an Omarchy plugin project |
|
|
336
314
|
| `omarchy_ui run FILE` | One Ruby entry point | Runs an application through the Omarchy/Quickshell host |
|
|
337
|
-
| `omarchy_ui bundle [DIRECTORY]` | Zero or one project directory |
|
|
338
|
-
| `omarchy_ui validate [DIRECTORY]` | Zero or one plugin directory | Stages the
|
|
315
|
+
| `omarchy_ui bundle [DIRECTORY]` | Zero or one project directory | Generates the distributable package and prints its location |
|
|
316
|
+
| `omarchy_ui validate [DIRECTORY]` | Zero or one plugin directory | Stages the plugin and delegates validation to `omarchy plugin validate` |
|
|
339
317
|
| `omarchy_ui push [DIRECTORY]` | Optional `--no-enable` and `--no-restart` | Atomically installs and activates a validated plugin |
|
|
340
318
|
| `omarchy_ui version` | None | Prints the installed Omarchy UI version |
|
|
341
319
|
|
|
@@ -344,38 +322,29 @@ not forward arbitrary Ruby arguments.
|
|
|
344
322
|
|
|
345
323
|
## Packaging
|
|
346
324
|
|
|
347
|
-
Create
|
|
325
|
+
Create the distributable package from the project root:
|
|
348
326
|
|
|
349
327
|
```bash
|
|
350
328
|
omarchy_ui bundle
|
|
351
|
-
./dist/my-application/run
|
|
352
329
|
```
|
|
353
330
|
|
|
354
|
-
|
|
331
|
+
Omarchy UI assembles the Ruby source and assets, Omarchy entry points, native host, checksummed
|
|
332
|
+
runtime, and only the Zui components used by the project. It then AOT-compiles the generated QML into
|
|
333
|
+
a content-addressed Qt module and discards the generated component, control, theme, and font source
|
|
334
|
+
tree. Applications receive a direct launcher; plugins must pass Omarchy validation before the
|
|
335
|
+
command succeeds. Omarchy UI pins Zui 0.0.10 so the compiled UI matches the Zui core embedded in the
|
|
336
|
+
deterministic mruby runtime.
|
|
355
337
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
├── BarWidget.qml # shell bar adapter
|
|
364
|
-
├── ControlNode.qml # generated Zui protocol renderer
|
|
365
|
-
├── Components/Builtins/ # only component adapters selected by Zui
|
|
366
|
-
├── Controls/ Theme/ Fonts/ # generated renderer dependencies
|
|
367
|
-
├── zui-tree-shake.json # auditable selected-component report
|
|
368
|
-
├── Commons@ Ui@ # links to Omarchy's shell modules
|
|
369
|
-
├── omarchy-ui-runtime # embedded mruby executable
|
|
370
|
-
├── omarchy-ui-runtime.sha256
|
|
371
|
-
├── RUNTIME_PROVENANCE.md
|
|
372
|
-
└── run
|
|
373
|
-
```
|
|
338
|
+
Current Omarchy manifests resolve entry points by filename, so a package retains four minimal loader
|
|
339
|
+
files: `App.qml`, `Service.qml`, `Panel.qml`, and `BarWidget.qml`. They contain only an import and a
|
|
340
|
+
compiled type instance; the application interface is stored in native `.so` artifacts. The package
|
|
341
|
+
also includes `omarchy-ui-qml-bundle.json` with the module URI and exact Qt version, plus
|
|
342
|
+
`omarchy-ui-qml-bundle.sha256` for artifact verification. `QML_PROVENANCE.md` explains the native
|
|
343
|
+
artifacts for package reviewers. Compiled packages should be built against the Qt version shipped by
|
|
344
|
+
their target Omarchy release.
|
|
374
345
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
adapter/runtime files before regenerating the minimal QML package. Omarchy UI pins Zui 0.0.10 so the
|
|
378
|
-
tree-shaken QML exactly matches the Zui core embedded in the deterministic mruby runtime.
|
|
346
|
+
The source `config.rb` is the build configuration. Omarchy UI generates plugin manifests from it;
|
|
347
|
+
developers never maintain a separate manifest.
|
|
379
348
|
|
|
380
349
|
## Component catalog
|
|
381
350
|
|
|
@@ -442,10 +411,12 @@ Zui DSL · state · events · bindings · scheduler
|
|
|
442
411
|
│
|
|
443
412
|
│ versioned JSON render, patch, event, and effect protocol
|
|
444
413
|
▼
|
|
445
|
-
Omarchy
|
|
414
|
+
Omarchy process bridge ───────────────── embedded mruby runtime
|
|
446
415
|
│
|
|
447
416
|
▼
|
|
448
|
-
Zui
|
|
417
|
+
Tree-shaken Zui host + component catalog
|
|
418
|
+
│
|
|
419
|
+
│ bundle: Qt AOT module + four loader shims
|
|
449
420
|
│
|
|
450
421
|
▼
|
|
451
422
|
App window · Omarchy panel · Omarchy bar widget
|
|
@@ -463,8 +434,9 @@ The dependency direction is deliberate:
|
|
|
463
434
|
- **Applications own** Ruby source, assets, state, behavior, and reusable UI modules.
|
|
464
435
|
|
|
465
436
|
At bundle, validation, and push time, Omarchy UI asks Zui to analyze the bundled Ruby syntax and
|
|
466
|
-
generate only the required component adapters plus their renderer dependencies.
|
|
467
|
-
host
|
|
437
|
+
generate only the required component adapters plus their renderer dependencies. Omarchy UI overlays
|
|
438
|
+
its host, compiles the complete generated QML graph into a content-addressed Qt module, and emits the
|
|
439
|
+
minimal loader files required by Omarchy. Read the full [QML ownership and adapter
|
|
468
440
|
boundary](docs/qml-support.md).
|
|
469
441
|
|
|
470
442
|
## Runtime integrity
|
|
@@ -504,11 +476,11 @@ bodies are never recorded. Per-plugin logs rotate at 128 KiB under
|
|
|
504
476
|
| `Ruby file not found` | Pass an existing entry point to `omarchy_ui run`, normally `main.rb` |
|
|
505
477
|
| `Omarchy QML module not found` | Confirm the Omarchy shell installation provides `/usr/share/omarchy/shell/Commons` and `Ui` |
|
|
506
478
|
| A component reports a missing module | Install or enable the Qt module required by that declared component |
|
|
507
|
-
| Plugin validation fails | Run `omarchy_ui validate` and correct the
|
|
479
|
+
| Plugin validation fails | Run `omarchy_ui validate` and correct the reported Ruby, asset, or generated-metadata error before pushing |
|
|
508
480
|
| A pushed plugin is installed but inactive | Run `omarchy plugin enable <id>` and `omarchy restart shell` after resolving the reported shell error |
|
|
509
|
-
| The bundle destination already exists | Review or move the
|
|
481
|
+
| The bundle destination already exists | Review or move the previously generated package before bundling again |
|
|
510
482
|
|
|
511
|
-
Omarchy UI reports unsupported properties, components, events, paths,
|
|
483
|
+
Omarchy UI reports unsupported properties, components, events, paths, generated metadata, and runtime
|
|
512
484
|
resources directly. Preserve the original error when opening an issue.
|
|
513
485
|
|
|
514
486
|
## Development
|
data/lib/omarchy_ui/cli.rb
CHANGED
|
@@ -27,7 +27,7 @@ module OmarchyUI
|
|
|
27
27
|
when "validate" then validate(arguments)
|
|
28
28
|
when "version", "--version", "-v" then @out.puts(OmarchyUI::VERSION); 0
|
|
29
29
|
else
|
|
30
|
-
@err.puts("Usage: omarchy_ui <new NAME|run FILE|bundle [DIRECTORY]|push [DIRECTORY]|validate [DIRECTORY]|version>")
|
|
30
|
+
@err.puts("Usage: omarchy_ui <new NAME [--plugin]|run FILE|bundle [DIRECTORY]|push [DIRECTORY]|validate [DIRECTORY]|version>")
|
|
31
31
|
command.nil? ? 0 : 64
|
|
32
32
|
end
|
|
33
33
|
rescue Interrupt
|
|
@@ -67,14 +67,30 @@ module OmarchyUI
|
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
def new_project(arguments)
|
|
70
|
+
plugin = !arguments.delete("--plugin").nil?
|
|
70
71
|
name = arguments.shift || raise(ArgumentError, "new requires a project name")
|
|
71
|
-
raise ArgumentError, "new accepts
|
|
72
|
+
raise ArgumentError, "new accepts a name and optional --plugin" unless arguments.empty?
|
|
72
73
|
destination = File.expand_path(slug(name))
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
kind = plugin ? :plugin : :application
|
|
75
|
+
Generator.new(path: destination, name: name, kind:, author: default_author).create
|
|
76
|
+
label = plugin ? "Omarchy-compliant plugin" : "self-contained Omarchy application"
|
|
77
|
+
@out.puts("Created #{label} in #{destination}")
|
|
75
78
|
0
|
|
76
79
|
end
|
|
77
80
|
|
|
81
|
+
def default_author
|
|
82
|
+
configured = ENV["OMARCHY_UI_AUTHOR"].to_s.strip
|
|
83
|
+
return configured unless configured.empty?
|
|
84
|
+
|
|
85
|
+
output, _error, status = Open3.capture3("git", "config", "--get", "user.name")
|
|
86
|
+
git_author = output.to_s.strip
|
|
87
|
+
return git_author if status.success? && !git_author.empty?
|
|
88
|
+
|
|
89
|
+
ENV["USER"].to_s.strip.then { |user| user.empty? ? "Omarchy UI Developer" : user }
|
|
90
|
+
rescue SystemCallError
|
|
91
|
+
"Omarchy UI Developer"
|
|
92
|
+
end
|
|
93
|
+
|
|
78
94
|
def slug(value)
|
|
79
95
|
result = value.to_s.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-|\-\z/, "")
|
|
80
96
|
raise ArgumentError, "name must contain letters or numbers" if result.empty?
|
|
@@ -83,6 +99,10 @@ module OmarchyUI
|
|
|
83
99
|
|
|
84
100
|
def validate(arguments)
|
|
85
101
|
source = File.expand_path(arguments.shift || Dir.pwd)
|
|
102
|
+
raise ArgumentError, "validate accepts one directory" unless arguments.empty?
|
|
103
|
+
config = ProjectConfig.load(source)
|
|
104
|
+
raise ArgumentError, "validate requires a plugin project" if config&.application?
|
|
105
|
+
|
|
86
106
|
with_staged_project(source) do |staging|
|
|
87
107
|
system("omarchy", "plugin", "validate", staging) ? 0 : 1
|
|
88
108
|
end
|
|
@@ -91,15 +111,22 @@ module OmarchyUI
|
|
|
91
111
|
def bundle_project(arguments)
|
|
92
112
|
source = File.expand_path(arguments.shift || Dir.pwd)
|
|
93
113
|
raise ArgumentError, "bundle accepts one directory" unless arguments.empty?
|
|
94
|
-
|
|
95
|
-
|
|
114
|
+
config = ProjectConfig.load(source)
|
|
115
|
+
entrypoint = config&.entrypoint || "main.rb"
|
|
116
|
+
raise ArgumentError, "#{entrypoint} not found: #{source}" unless File.file?(File.join(source, entrypoint))
|
|
117
|
+
precompiled = config.nil? && compiled_package?(source)
|
|
118
|
+
destination = File.join(source, "dist", config&.slug || File.basename(source))
|
|
96
119
|
raise ArgumentError, "bundle destination already exists: #{destination}" if File.exist?(destination)
|
|
97
120
|
FileUtils.mkdir_p(destination)
|
|
98
|
-
entries = application_entries(source)
|
|
121
|
+
entries = precompiled ? package_entries(source) : application_entries(source, config:)
|
|
99
122
|
FileUtils.cp_r(entries.map { |entry| File.join(source, entry) }, destination)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
123
|
+
unless precompiled
|
|
124
|
+
config.write_manifest(destination) if config&.plugin?
|
|
125
|
+
bundle_ruby_entrypoint(source, destination, entrypoint:)
|
|
126
|
+
Runtime.install_package(destination, compiled: !config.nil?)
|
|
127
|
+
end
|
|
128
|
+
plugin = config ? config.plugin? : File.file?(File.join(destination, "manifest.json"))
|
|
129
|
+
if plugin
|
|
103
130
|
raise ArgumentError, "bundled plugin validation failed" unless system("omarchy", "plugin", "validate", destination)
|
|
104
131
|
@out.puts("Bundled plugin in #{destination}")
|
|
105
132
|
return 0
|
|
@@ -115,7 +142,9 @@ module OmarchyUI
|
|
|
115
142
|
app_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
116
143
|
export OMARCHY_UI_RUNTIME="$app_dir/omarchy-ui-runtime"
|
|
117
144
|
export OMARCHY_UI_PROJECT_DIR="$app_dir"
|
|
118
|
-
export OMARCHY_UI_RUBY_PROGRAM="$app_dir
|
|
145
|
+
export OMARCHY_UI_RUBY_PROGRAM="$app_dir/#{entrypoint}"
|
|
146
|
+
export QML2_IMPORT_PATH="$app_dir${QML2_IMPORT_PATH:+:$QML2_IMPORT_PATH}"
|
|
147
|
+
export QML_IMPORT_PATH="$app_dir${QML_IMPORT_PATH:+:$QML_IMPORT_PATH}"
|
|
119
148
|
export QT_LOGGING_RULES="${QT_LOGGING_RULES:+$QT_LOGGING_RULES;}qt.qpa.services.warning=false"
|
|
120
149
|
exec quickshell --path "$app_dir/App.qml"
|
|
121
150
|
SH
|
|
@@ -131,17 +160,33 @@ module OmarchyUI
|
|
|
131
160
|
[ENV["QT_LOGGING_RULES"], "qt.qpa.services.warning=false"].compact.reject(&:empty?).join(";")
|
|
132
161
|
end
|
|
133
162
|
|
|
134
|
-
def application_entries(source)
|
|
163
|
+
def application_entries(source, config: ProjectConfig.load(source))
|
|
135
164
|
generated = Runtime::GENERATED_ENTRIES + Runtime::AUDIT_FILES + %w[run Commons Ui]
|
|
165
|
+
generated.concat([ProjectConfig::FILE_NAME, "manifest.json"]) if config
|
|
136
166
|
Dir.children(source).reject { |entry| %w[.git dist].include?(entry) || generated.include?(entry) }
|
|
137
167
|
end
|
|
138
168
|
|
|
169
|
+
def package_entries(source)
|
|
170
|
+
Dir.children(source).reject { |entry| %w[.git dist].include?(entry) }
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def compiled_package?(source)
|
|
174
|
+
report = File.join(source, QmlCompiler::REPORT)
|
|
175
|
+
return false unless File.file?(report)
|
|
176
|
+
|
|
177
|
+
QmlCompiler.verify!(source)
|
|
178
|
+
true
|
|
179
|
+
end
|
|
180
|
+
|
|
139
181
|
def push(arguments)
|
|
140
182
|
enable = !arguments.delete("--no-enable")
|
|
141
183
|
restart = !arguments.delete("--no-restart")
|
|
142
184
|
source = File.expand_path(arguments.shift || Dir.pwd)
|
|
185
|
+
raise ArgumentError, "push accepts one directory" unless arguments.empty?
|
|
186
|
+
config = ProjectConfig.load(source)
|
|
187
|
+
raise ArgumentError, "push requires a plugin project" if config&.application?
|
|
143
188
|
manifest_path = File.join(source, "manifest.json")
|
|
144
|
-
manifest = JSON.parse(File.read(manifest_path))
|
|
189
|
+
manifest = config ? config.manifest : JSON.parse(File.read(manifest_path))
|
|
145
190
|
plugin_id = manifest.fetch("id")
|
|
146
191
|
raise ArgumentError, "invalid plugin id" unless VALID_ID.match?(plugin_id)
|
|
147
192
|
plugin_root = File.expand_path("~/.config/omarchy/plugins")
|
|
@@ -184,12 +229,18 @@ module OmarchyUI
|
|
|
184
229
|
|
|
185
230
|
def stage_project(source, parent: nil, prefix: ".omarchy-ui-staging-")
|
|
186
231
|
raise ArgumentError, "project directory not found: #{source}" unless File.directory?(source)
|
|
232
|
+
config = ProjectConfig.load(source)
|
|
233
|
+
precompiled = config.nil? && compiled_package?(source)
|
|
187
234
|
staging = parent ? Dir.mktmpdir(prefix, parent) : Dir.mktmpdir(prefix)
|
|
188
|
-
entries = application_entries(source)
|
|
235
|
+
entries = precompiled ? package_entries(source) : application_entries(source, config:)
|
|
189
236
|
FileUtils.cp_r(entries.map { |entry| File.join(source, entry) }, staging) unless entries.empty?
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
237
|
+
unless precompiled
|
|
238
|
+
config.write_manifest(staging) if config&.plugin?
|
|
239
|
+
entrypoint = config&.entrypoint || "main.rb"
|
|
240
|
+
if File.file?(File.join(staging, entrypoint))
|
|
241
|
+
bundle_ruby_entrypoint(source, staging, entrypoint:)
|
|
242
|
+
Runtime.install_package(staging, compiled: !config.nil?)
|
|
243
|
+
end
|
|
193
244
|
end
|
|
194
245
|
staging
|
|
195
246
|
rescue StandardError
|
|
@@ -197,9 +248,11 @@ module OmarchyUI
|
|
|
197
248
|
raise
|
|
198
249
|
end
|
|
199
250
|
|
|
200
|
-
def bundle_ruby_entrypoint(source, destination)
|
|
201
|
-
|
|
202
|
-
File.
|
|
251
|
+
def bundle_ruby_entrypoint(source, destination, entrypoint: "main.rb")
|
|
252
|
+
source_entrypoint = File.join(source, entrypoint)
|
|
253
|
+
destination_entrypoint = File.join(destination, entrypoint)
|
|
254
|
+
FileUtils.mkdir_p(File.dirname(destination_entrypoint))
|
|
255
|
+
File.write(destination_entrypoint, SourceBundle.new(source_entrypoint, root: source).call)
|
|
203
256
|
end
|
|
204
257
|
|
|
205
258
|
def activate_plugin(plugin_id)
|
data/lib/omarchy_ui/generator.rb
CHANGED
|
@@ -4,9 +4,15 @@ require "fileutils"
|
|
|
4
4
|
|
|
5
5
|
module OmarchyUI
|
|
6
6
|
class Generator
|
|
7
|
-
|
|
7
|
+
PROJECT_KINDS = %i[application plugin].freeze
|
|
8
|
+
|
|
9
|
+
def initialize(path:, name: nil, kind: :application, plugin_id: nil, author: nil)
|
|
8
10
|
@path = File.expand_path(path)
|
|
9
11
|
@name = name || File.basename(@path).split(/[-_]/).map(&:capitalize).join(" ")
|
|
12
|
+
@kind = kind.to_sym
|
|
13
|
+
@plugin_id = plugin_id || "local.#{project_slug}"
|
|
14
|
+
@author = author.to_s.strip.empty? ? "Omarchy UI Developer" : author.to_s.strip
|
|
15
|
+
raise ArgumentError, "unsupported project kind: #{@kind}" unless PROJECT_KINDS.include?(@kind)
|
|
10
16
|
end
|
|
11
17
|
|
|
12
18
|
def create
|
|
@@ -14,9 +20,11 @@ module OmarchyUI
|
|
|
14
20
|
created = true
|
|
15
21
|
FileUtils.mkdir_p(File.join(@path, "components"))
|
|
16
22
|
File.write(File.join(@path, "main.rb"), main_program)
|
|
17
|
-
File.write(File.join(@path, "app.rb"), application_program)
|
|
23
|
+
File.write(File.join(@path, "app.rb"), application_program) unless plugin?
|
|
24
|
+
File.write(File.join(@path, ProjectConfig::FILE_NAME), project_config)
|
|
18
25
|
File.write(File.join(@path, "components", "welcome.rb"), welcome_component)
|
|
19
26
|
File.write(File.join(@path, "README.md"), readme)
|
|
27
|
+
File.write(File.join(@path, "LICENSE"), license)
|
|
20
28
|
@path
|
|
21
29
|
rescue StandardError
|
|
22
30
|
FileUtils.remove_entry(@path) if created && File.directory?(@path)
|
|
@@ -26,6 +34,8 @@ module OmarchyUI
|
|
|
26
34
|
private
|
|
27
35
|
|
|
28
36
|
def main_program
|
|
37
|
+
return plugin_program if plugin?
|
|
38
|
+
|
|
29
39
|
<<~RUBY
|
|
30
40
|
# frozen_string_literal: true
|
|
31
41
|
|
|
@@ -36,6 +46,33 @@ module OmarchyUI
|
|
|
36
46
|
RUBY
|
|
37
47
|
end
|
|
38
48
|
|
|
49
|
+
def plugin_program
|
|
50
|
+
<<~RUBY
|
|
51
|
+
# frozen_string_literal: true
|
|
52
|
+
|
|
53
|
+
require "omarchy_ui"
|
|
54
|
+
require_relative "components/welcome"
|
|
55
|
+
|
|
56
|
+
OmarchyUI.plugin(ui: #{constant_name}::UI) do
|
|
57
|
+
bar_widget do
|
|
58
|
+
row spacing: 7 do
|
|
59
|
+
text "#{@name}"
|
|
60
|
+
end
|
|
61
|
+
on_click { open_panel :#{surface_name} }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
panel :#{surface_name} do
|
|
65
|
+
scroll width: 560, height: 620 do
|
|
66
|
+
welcome_card(
|
|
67
|
+
title: "#{@name}",
|
|
68
|
+
message: "Your Omarchy plugin is ready. Build the interface with Ruby and Zui components."
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
RUBY
|
|
74
|
+
end
|
|
75
|
+
|
|
39
76
|
def application_program
|
|
40
77
|
<<~RUBY
|
|
41
78
|
# frozen_string_literal: true
|
|
@@ -80,6 +117,8 @@ module OmarchyUI
|
|
|
80
117
|
end
|
|
81
118
|
|
|
82
119
|
def readme
|
|
120
|
+
return plugin_readme if plugin?
|
|
121
|
+
|
|
83
122
|
<<~MARKDOWN
|
|
84
123
|
# #{@name}
|
|
85
124
|
|
|
@@ -92,10 +131,98 @@ module OmarchyUI
|
|
|
92
131
|
```
|
|
93
132
|
|
|
94
133
|
Reusable Ruby UI components live in `components/` and load with ordinary Ruby
|
|
95
|
-
`require_relative`.
|
|
134
|
+
`require_relative`. Project identity and build settings live in `config.rb`.
|
|
135
|
+
|
|
136
|
+
## Build
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
omarchy_ui bundle
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Omarchy UI tree-shakes and compiles the UI, then generates the complete self-contained
|
|
143
|
+
application package required by Omarchy.
|
|
144
|
+
MARKDOWN
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def plugin_readme
|
|
148
|
+
<<~MARKDOWN
|
|
149
|
+
# #{@name}
|
|
150
|
+
|
|
151
|
+
A pure Ruby Omarchy plugin built with Omarchy UI and Zui.
|
|
152
|
+
|
|
153
|
+
## Validate
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
omarchy_ui validate
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Install for development
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
omarchy_ui push
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Build
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
omarchy_ui bundle
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Write the plugin interface and behavior in Ruby under `main.rb` and `components/`.
|
|
172
|
+
Public plugin identity and release metadata live in `config.rb`. Omarchy UI tree-shakes and
|
|
173
|
+
compiles the UI, then generates and validates the complete Omarchy-compatible plugin package.
|
|
96
174
|
MARKDOWN
|
|
97
175
|
end
|
|
98
176
|
|
|
177
|
+
def project_config
|
|
178
|
+
config = ProjectConfig.generate(
|
|
179
|
+
name: @name,
|
|
180
|
+
slug: project_slug,
|
|
181
|
+
kind: @kind,
|
|
182
|
+
author: @author,
|
|
183
|
+
plugin_id: @plugin_id
|
|
184
|
+
)
|
|
185
|
+
config
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def license
|
|
189
|
+
<<~TEXT
|
|
190
|
+
MIT License
|
|
191
|
+
|
|
192
|
+
Copyright (c) #{Time.now.year} #{@author}
|
|
193
|
+
|
|
194
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
195
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
196
|
+
in the Software without restriction, including without limitation the rights
|
|
197
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
198
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
199
|
+
furnished to do so, subject to the following conditions:
|
|
200
|
+
|
|
201
|
+
The above copyright notice and this permission notice shall be included in all
|
|
202
|
+
copies or substantial portions of the Software.
|
|
203
|
+
|
|
204
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
205
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
206
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
207
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
208
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
209
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
210
|
+
SOFTWARE.
|
|
211
|
+
TEXT
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def plugin?
|
|
215
|
+
@kind == :plugin
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def project_slug
|
|
219
|
+
File.basename(@path).downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-|\-\z/, "")
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def surface_name
|
|
223
|
+
project_slug.tr("-", "_")
|
|
224
|
+
end
|
|
225
|
+
|
|
99
226
|
def constant_name
|
|
100
227
|
@name.gsub(/[^a-zA-Z0-9]+/, " ").split.map(&:capitalize).join.then do |name|
|
|
101
228
|
name.empty? || name.match?(/\A\d/) ? "OmarchyApplication" : name
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module OmarchyUI
|
|
6
|
+
class ProjectConfig
|
|
7
|
+
FILE_NAME = "config.rb"
|
|
8
|
+
PROJECT_TYPES = %i[application plugin].freeze
|
|
9
|
+
SLUG = /\A[a-z0-9]+(?:-[a-z0-9]+)*\z/
|
|
10
|
+
SECTIONS = %i[left center right].freeze
|
|
11
|
+
|
|
12
|
+
class Builder
|
|
13
|
+
FIELDS = %i[type id name slug version author license description entrypoint].freeze
|
|
14
|
+
|
|
15
|
+
FIELDS.each do |field|
|
|
16
|
+
define_method(field) { |value| @values[field] = value }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
@values = {}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def bar_widget(&definition)
|
|
24
|
+
widget = WidgetBuilder.new
|
|
25
|
+
widget.instance_eval(&definition)
|
|
26
|
+
@values[:bar_widget] = widget.to_h
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def build(path: FILE_NAME)
|
|
30
|
+
ProjectConfig.new(@values, path:).tap(&:validate!)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class WidgetBuilder
|
|
35
|
+
FIELDS = %i[display_name description category default_section].freeze
|
|
36
|
+
|
|
37
|
+
FIELDS.each do |field|
|
|
38
|
+
define_method(field) { |value| @values[field] = value }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def initialize
|
|
42
|
+
@values = {}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def to_h
|
|
46
|
+
@values.dup
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
attr_reader :path
|
|
51
|
+
|
|
52
|
+
def self.define(&definition)
|
|
53
|
+
raise ArgumentError, "project config requires a block" unless definition
|
|
54
|
+
|
|
55
|
+
Builder.new.tap { |builder| builder.instance_eval(&definition) }.build
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.generate(name:, slug:, kind:, author:, plugin_id: nil)
|
|
59
|
+
type = kind.to_sym
|
|
60
|
+
raise ArgumentError, "unsupported project type: #{type}" unless PROJECT_TYPES.include?(type)
|
|
61
|
+
|
|
62
|
+
lines = [
|
|
63
|
+
"# frozen_string_literal: true",
|
|
64
|
+
"",
|
|
65
|
+
"OmarchyUI.configure do",
|
|
66
|
+
" type #{type.inspect}"
|
|
67
|
+
]
|
|
68
|
+
lines << " id #{(plugin_id || "local.#{slug}").inspect}" if type == :plugin
|
|
69
|
+
lines.concat([
|
|
70
|
+
" name #{name.inspect}",
|
|
71
|
+
" slug #{slug.inspect}",
|
|
72
|
+
" version \"0.1.0\""
|
|
73
|
+
])
|
|
74
|
+
if type == :plugin
|
|
75
|
+
lines.concat([
|
|
76
|
+
" author #{author.inspect}",
|
|
77
|
+
" license \"MIT\"",
|
|
78
|
+
" description #{"A Ruby-powered #{name} plugin generated by Omarchy UI.".inspect}",
|
|
79
|
+
" entrypoint \"main.rb\"",
|
|
80
|
+
"",
|
|
81
|
+
" bar_widget do",
|
|
82
|
+
" display_name #{name.inspect}",
|
|
83
|
+
" description #{"Open #{name}.".inspect}",
|
|
84
|
+
" category \"System\"",
|
|
85
|
+
" default_section :right",
|
|
86
|
+
" end"
|
|
87
|
+
])
|
|
88
|
+
else
|
|
89
|
+
lines << " entrypoint \"main.rb\""
|
|
90
|
+
end
|
|
91
|
+
lines << "end"
|
|
92
|
+
lines.join("\n") + "\n"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.load(root)
|
|
96
|
+
path = File.join(File.expand_path(root), FILE_NAME)
|
|
97
|
+
return nil unless File.file?(path)
|
|
98
|
+
|
|
99
|
+
value = Module.new.module_eval(File.read(path), path, 1)
|
|
100
|
+
unless value.is_a?(ProjectConfig)
|
|
101
|
+
raise ArgumentError, "#{FILE_NAME} must return an OmarchyUI::ProjectConfig"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
new(value.to_h, path:).tap(&:validate!)
|
|
105
|
+
rescue SyntaxError, NameError, NoMethodError => error
|
|
106
|
+
raise ArgumentError, "invalid #{FILE_NAME}: #{error.message}"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def initialize(data, path:)
|
|
110
|
+
@data = data
|
|
111
|
+
@path = path
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def type
|
|
115
|
+
value = @data[:type]
|
|
116
|
+
value.respond_to?(:to_sym) ? value.to_sym : value
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def application? = type == :application
|
|
120
|
+
def plugin? = type == :plugin
|
|
121
|
+
def name = @data.fetch(:name)
|
|
122
|
+
def slug = @data.fetch(:slug)
|
|
123
|
+
def entrypoint = @data.fetch(:entrypoint)
|
|
124
|
+
|
|
125
|
+
def to_h
|
|
126
|
+
@data.dup
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def manifest
|
|
130
|
+
raise ArgumentError, "project config describes an application, not a plugin" unless plugin?
|
|
131
|
+
|
|
132
|
+
widget = @data.fetch(:bar_widget)
|
|
133
|
+
{
|
|
134
|
+
"schemaVersion" => 1,
|
|
135
|
+
"id" => @data.fetch(:id),
|
|
136
|
+
"name" => name,
|
|
137
|
+
"version" => @data.fetch(:version),
|
|
138
|
+
"author" => @data.fetch(:author),
|
|
139
|
+
"license" => @data.fetch(:license),
|
|
140
|
+
"description" => @data.fetch(:description),
|
|
141
|
+
"kinds" => ["service", "bar-widget", "panel"],
|
|
142
|
+
"keepLoaded" => true,
|
|
143
|
+
"entryPoints" => {
|
|
144
|
+
"service" => "Service.qml",
|
|
145
|
+
"barWidget" => "BarWidget.qml",
|
|
146
|
+
"panel" => "Panel.qml"
|
|
147
|
+
},
|
|
148
|
+
"barWidget" => {
|
|
149
|
+
"displayName" => widget.fetch(:display_name),
|
|
150
|
+
"description" => widget.fetch(:description),
|
|
151
|
+
"category" => widget.fetch(:category),
|
|
152
|
+
"allowMultiple" => false,
|
|
153
|
+
"defaultSection" => widget.fetch(:default_section).to_s
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def write_manifest(destination)
|
|
159
|
+
File.write(File.join(destination, "manifest.json"), JSON.pretty_generate(manifest) + "\n")
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def validate!
|
|
163
|
+
raise ArgumentError, "invalid project config" unless @data.is_a?(Hash)
|
|
164
|
+
raise ArgumentError, "invalid project type" unless PROJECT_TYPES.include?(type)
|
|
165
|
+
required_string!(:name)
|
|
166
|
+
required_string!(:slug)
|
|
167
|
+
raise ArgumentError, "invalid project slug" unless SLUG.match?(@data[:slug])
|
|
168
|
+
required_string!(:version)
|
|
169
|
+
required_string!(:entrypoint)
|
|
170
|
+
validate_entrypoint!
|
|
171
|
+
validate_plugin! if plugin?
|
|
172
|
+
self
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
def required_string!(key, object = @data)
|
|
178
|
+
value = object[key]
|
|
179
|
+
raise ArgumentError, "invalid project config field: #{key}" unless value.is_a?(String) && !value.strip.empty?
|
|
180
|
+
|
|
181
|
+
value
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def validate_entrypoint!
|
|
185
|
+
parts = entrypoint.split(/[\\\/]/)
|
|
186
|
+
if entrypoint.start_with?("/") || parts.empty? || parts.include?("..") || !entrypoint.end_with?(".rb")
|
|
187
|
+
raise ArgumentError, "invalid project entrypoint"
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def validate_plugin!
|
|
192
|
+
%i[id author license description].each { |key| required_string!(key) }
|
|
193
|
+
raise ArgumentError, "invalid plugin id" unless VALID_ID.match?(@data[:id])
|
|
194
|
+
|
|
195
|
+
widget = @data[:bar_widget]
|
|
196
|
+
raise ArgumentError, "plugin project config is missing bar widget metadata" unless widget.is_a?(Hash)
|
|
197
|
+
|
|
198
|
+
%i[display_name description category].each { |key| required_string!(key, widget) }
|
|
199
|
+
section = widget[:default_section]
|
|
200
|
+
valid_section = section.respond_to?(:to_sym) && SECTIONS.include?(section.to_sym)
|
|
201
|
+
raise ArgumentError, "invalid default bar section" unless valid_section
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def self.configure(&definition)
|
|
206
|
+
ProjectConfig.define(&definition)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "json"
|
|
6
|
+
require "open3"
|
|
7
|
+
require "pathname"
|
|
8
|
+
require "tmpdir"
|
|
9
|
+
|
|
10
|
+
module OmarchyUI
|
|
11
|
+
class QmlCompiler
|
|
12
|
+
FORMAT_VERSION = 1
|
|
13
|
+
ENTRY_TYPES = {
|
|
14
|
+
"App.qml" => "App",
|
|
15
|
+
"BarWidget.qml" => "BarWidget",
|
|
16
|
+
"Panel.qml" => "Panel",
|
|
17
|
+
"Service.qml" => "Service"
|
|
18
|
+
}.freeze
|
|
19
|
+
SOURCE_ENTRIES = (ENTRY_TYPES.keys + %w[ControlNode.qml Components Controls Theme Fonts]).freeze
|
|
20
|
+
COMPILED_ROOT = "OmarchyUI"
|
|
21
|
+
REPORT = "omarchy-ui-qml-bundle.json"
|
|
22
|
+
CHECKSUM = "omarchy-ui-qml-bundle.sha256"
|
|
23
|
+
PROVENANCE = "QML_PROVENANCE.md"
|
|
24
|
+
|
|
25
|
+
def self.compile!(path)
|
|
26
|
+
new(path).compile!
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.verify!(path)
|
|
30
|
+
new(path).verify!
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def initialize(path)
|
|
34
|
+
@path = File.expand_path(path)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def compile!
|
|
38
|
+
validate_sources!
|
|
39
|
+
tools = build_tools
|
|
40
|
+
qt_version = qt_version!(tools.fetch(:qtpaths))
|
|
41
|
+
|
|
42
|
+
Dir.mktmpdir("omarchy-ui-qml-compile-") do |temporary|
|
|
43
|
+
source = File.join(temporary, "source")
|
|
44
|
+
build = File.join(temporary, "build")
|
|
45
|
+
stage_sources(source)
|
|
46
|
+
qml_files = relative_files(source, "**/*.qml")
|
|
47
|
+
resources = relative_files(source, "Fonts/**/*") +
|
|
48
|
+
%w[Controls/qmldir Theme/qmldir].select { |entry| File.file?(File.join(source, entry)) }
|
|
49
|
+
fingerprint = source_fingerprint(source, qml_files + resources, qt_version:)
|
|
50
|
+
segment = "B#{fingerprint[0, 20]}"
|
|
51
|
+
uri = "OmarchyUI.Bundles.#{segment}"
|
|
52
|
+
target = "omarchy_ui_bundle_#{segment.downcase}"
|
|
53
|
+
module_path = File.join(*uri.split("."))
|
|
54
|
+
|
|
55
|
+
File.write(File.join(source, "CMakeLists.txt"), cmake_project(
|
|
56
|
+
qml_files:,
|
|
57
|
+
resources: resources.sort,
|
|
58
|
+
uri:,
|
|
59
|
+
target:,
|
|
60
|
+
module_path:
|
|
61
|
+
))
|
|
62
|
+
configure!(tools.fetch(:qt_cmake), source, build)
|
|
63
|
+
build!(tools.fetch(:cmake), build)
|
|
64
|
+
deploy!(
|
|
65
|
+
build:,
|
|
66
|
+
module_path:,
|
|
67
|
+
target:,
|
|
68
|
+
uri:,
|
|
69
|
+
fingerprint:,
|
|
70
|
+
qml_files:,
|
|
71
|
+
resources:,
|
|
72
|
+
source:,
|
|
73
|
+
expected_qt_version: qt_version
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def verify!
|
|
79
|
+
report_path = File.join(@path, REPORT)
|
|
80
|
+
raise ArgumentError, "compiled QML report is missing: #{report_path}" unless File.file?(report_path)
|
|
81
|
+
|
|
82
|
+
report = JSON.parse(File.read(report_path))
|
|
83
|
+
unless report["format"] == "qt-aot-qml-module" && report["format_version"] == FORMAT_VERSION
|
|
84
|
+
raise ArgumentError, "unsupported compiled QML package format"
|
|
85
|
+
end
|
|
86
|
+
ENTRY_TYPES.each_key do |entry|
|
|
87
|
+
raise ArgumentError, "compiled QML entry shim is missing: #{entry}" unless File.file?(File.join(@path, entry))
|
|
88
|
+
end
|
|
89
|
+
module_path = safe_relative_path(report.fetch("module_path"))
|
|
90
|
+
raise ArgumentError, "compiled QML qmldir is missing" unless File.file?(File.join(@path, module_path, "qmldir"))
|
|
91
|
+
|
|
92
|
+
artifacts = report.fetch("artifacts")
|
|
93
|
+
raise ArgumentError, "compiled QML package must contain two libraries" unless artifacts.length == 2
|
|
94
|
+
artifacts.each do |artifact|
|
|
95
|
+
relative = safe_relative_path(artifact.fetch("path"))
|
|
96
|
+
unless relative.start_with?("#{module_path}/") && relative.end_with?(".so")
|
|
97
|
+
raise ArgumentError, "compiled QML artifact is outside its module: #{relative}"
|
|
98
|
+
end
|
|
99
|
+
absolute = File.join(@path, relative)
|
|
100
|
+
raise ArgumentError, "compiled QML artifact is missing: #{relative}" unless File.file?(absolute)
|
|
101
|
+
unless File.size(absolute) == artifact.fetch("bytes") &&
|
|
102
|
+
Digest::SHA256.file(absolute).hexdigest == artifact.fetch("sha256")
|
|
103
|
+
raise ArgumentError, "compiled QML artifact checksum mismatch: #{relative}"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
report
|
|
107
|
+
rescue JSON::ParserError, KeyError, TypeError => error
|
|
108
|
+
raise ArgumentError, "invalid compiled QML report: #{error.message}"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def safe_relative_path(path)
|
|
114
|
+
value = path.to_s
|
|
115
|
+
clean = File.expand_path(value, @path)
|
|
116
|
+
unless !value.empty? && !Pathname.new(value).absolute? && clean.start_with?("#{@path}/")
|
|
117
|
+
raise ArgumentError, "unsafe compiled QML path: #{value.inspect}"
|
|
118
|
+
end
|
|
119
|
+
clean.delete_prefix("#{@path}/")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def validate_sources!
|
|
123
|
+
missing = ENTRY_TYPES.keys.reject { |entry| File.file?(File.join(@path, entry)) }
|
|
124
|
+
missing << "ControlNode.qml" unless File.file?(File.join(@path, "ControlNode.qml"))
|
|
125
|
+
return if missing.empty?
|
|
126
|
+
|
|
127
|
+
raise ArgumentError, "cannot compile QML; generated files are missing: #{missing.join(', ')}"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def build_tools
|
|
131
|
+
{
|
|
132
|
+
qt_cmake: find_executable("/usr/lib/qt6/bin/qt-cmake", "qt-cmake"),
|
|
133
|
+
qtpaths: find_executable("/usr/lib/qt6/bin/qtpaths", "qtpaths6", "qtpaths"),
|
|
134
|
+
cmake: find_executable("cmake"),
|
|
135
|
+
ninja: find_executable("ninja"),
|
|
136
|
+
compiler: find_executable("c++", "g++", "clang++")
|
|
137
|
+
}
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def qt_version!(qtpaths)
|
|
141
|
+
output, error, status = Open3.capture3(qtpaths, "--query", "QT_VERSION")
|
|
142
|
+
version = output.strip
|
|
143
|
+
return version if status.success? && !version.empty?
|
|
144
|
+
|
|
145
|
+
detail = error.strip
|
|
146
|
+
raise ArgumentError, "could not determine the Qt build version#{detail.empty? ? '' : ": #{detail}"}"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def find_executable(*candidates)
|
|
150
|
+
candidates.each do |candidate|
|
|
151
|
+
if candidate.include?(File::SEPARATOR)
|
|
152
|
+
return candidate if File.file?(candidate) && File.executable?(candidate)
|
|
153
|
+
next
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).each do |directory|
|
|
157
|
+
path = File.join(directory, candidate)
|
|
158
|
+
return path if File.file?(path) && File.executable?(path)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
raise ArgumentError, "compiled QML packaging requires #{candidates.first}"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def stage_sources(destination)
|
|
165
|
+
FileUtils.mkdir_p(destination)
|
|
166
|
+
SOURCE_ENTRIES.each do |entry|
|
|
167
|
+
source = File.join(@path, entry)
|
|
168
|
+
next unless File.exist?(source)
|
|
169
|
+
|
|
170
|
+
FileUtils.cp_r(source, File.join(destination, entry))
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def relative_files(root, pattern)
|
|
175
|
+
Dir.glob(File.join(root, pattern), File::FNM_DOTMATCH)
|
|
176
|
+
.select { |path| File.file?(path) }
|
|
177
|
+
.map { |path| path.delete_prefix("#{root}/") }
|
|
178
|
+
.sort
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def source_fingerprint(root, files, qt_version:)
|
|
182
|
+
digest = Digest::SHA256.new
|
|
183
|
+
digest << "omarchy-ui-qml-bundle\0" << FORMAT_VERSION.to_s << "\0"
|
|
184
|
+
digest << "qt\0" << qt_version << "\0"
|
|
185
|
+
files.sort.each do |relative|
|
|
186
|
+
digest << relative << "\0" << File.binread(File.join(root, relative)) << "\0"
|
|
187
|
+
end
|
|
188
|
+
digest.hexdigest
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def cmake_project(qml_files:, resources:, uri:, target:, module_path:)
|
|
192
|
+
<<~CMAKE
|
|
193
|
+
cmake_minimum_required(VERSION 3.21)
|
|
194
|
+
|
|
195
|
+
project(OmarchyUICompiledBundle LANGUAGES CXX)
|
|
196
|
+
|
|
197
|
+
find_package(Qt6 6.8 REQUIRED COMPONENTS Core Qml Quick QuickControls2)
|
|
198
|
+
qt_standard_project_setup(REQUIRES 6.8)
|
|
199
|
+
|
|
200
|
+
qt_add_library(#{target} SHARED)
|
|
201
|
+
qt_add_qml_module(#{target}
|
|
202
|
+
URI #{uri}
|
|
203
|
+
VERSION 1.0
|
|
204
|
+
OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/#{module_path}"
|
|
205
|
+
QML_FILES
|
|
206
|
+
#{cmake_entries(qml_files)}
|
|
207
|
+
RESOURCES
|
|
208
|
+
#{cmake_entries(resources)}
|
|
209
|
+
DISCARD_QML_CONTENTS
|
|
210
|
+
NO_LINT
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
set(module_output "${CMAKE_BINARY_DIR}/#{module_path}")
|
|
214
|
+
set_target_properties(
|
|
215
|
+
#{target}
|
|
216
|
+
#{target}plugin
|
|
217
|
+
PROPERTIES
|
|
218
|
+
LIBRARY_OUTPUT_DIRECTORY "${module_output}"
|
|
219
|
+
BUILD_RPATH "\\$ORIGIN"
|
|
220
|
+
BUILD_WITH_INSTALL_RPATH TRUE
|
|
221
|
+
INSTALL_RPATH "\\$ORIGIN"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
target_compile_options(
|
|
225
|
+
#{target}
|
|
226
|
+
PRIVATE
|
|
227
|
+
"-ffile-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=."
|
|
228
|
+
"-ffile-prefix-map=${CMAKE_CURRENT_BINARY_DIR}=."
|
|
229
|
+
)
|
|
230
|
+
target_compile_options(
|
|
231
|
+
#{target}plugin
|
|
232
|
+
PRIVATE
|
|
233
|
+
"-ffile-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=."
|
|
234
|
+
"-ffile-prefix-map=${CMAKE_CURRENT_BINARY_DIR}=."
|
|
235
|
+
)
|
|
236
|
+
target_link_options(#{target} PRIVATE "-Wl,--build-id=none")
|
|
237
|
+
target_link_options(#{target}plugin PRIVATE "-Wl,--build-id=none")
|
|
238
|
+
target_link_libraries(
|
|
239
|
+
#{target}
|
|
240
|
+
PRIVATE Qt6::Core Qt6::Qml Qt6::Quick Qt6::QuickControls2
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
file(WRITE "${CMAKE_BINARY_DIR}/qt-version.txt" "${Qt6Core_VERSION}\n")
|
|
244
|
+
CMAKE
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def cmake_entries(entries)
|
|
248
|
+
entries.map { |entry| " #{cmake_string(entry)}" }.join("\n")
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def cmake_string(value)
|
|
252
|
+
escaped = value.gsub("\\", "\\\\").gsub('"', '\\"')
|
|
253
|
+
%Q("#{escaped}")
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def configure!(qt_cmake, source, build)
|
|
257
|
+
run_build_command!(
|
|
258
|
+
"QML configure",
|
|
259
|
+
qt_cmake,
|
|
260
|
+
"-S", source,
|
|
261
|
+
"-B", build,
|
|
262
|
+
"-G", "Ninja",
|
|
263
|
+
"-DCMAKE_BUILD_TYPE=Release"
|
|
264
|
+
)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def build!(cmake, build)
|
|
268
|
+
run_build_command!("QML compilation", cmake, "--build", build, "--parallel")
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def run_build_command!(label, *command)
|
|
272
|
+
environment = {
|
|
273
|
+
"LC_ALL" => "C.UTF-8",
|
|
274
|
+
"SOURCE_DATE_EPOCH" => "1",
|
|
275
|
+
"TZ" => "UTC"
|
|
276
|
+
}
|
|
277
|
+
output, error, status = Open3.capture3(environment, *command)
|
|
278
|
+
return if status.success?
|
|
279
|
+
|
|
280
|
+
detail = "#{output}\n#{error}".lines.last(40).join.strip
|
|
281
|
+
raise ArgumentError, "#{label} failed#{detail.empty? ? '' : ":\n#{detail}"}"
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def deploy!(build:, module_path:, target:, uri:, fingerprint:, qml_files:, resources:, source:,
|
|
285
|
+
expected_qt_version:)
|
|
286
|
+
built_module = File.join(build, module_path)
|
|
287
|
+
artifact_names = ["lib#{target}.so", "lib#{target}plugin.so", "qmldir"]
|
|
288
|
+
missing = artifact_names.reject { |name| File.file?(File.join(built_module, name)) }
|
|
289
|
+
unless missing.empty?
|
|
290
|
+
raise ArgumentError, "QML compilation did not produce: #{missing.join(', ')}"
|
|
291
|
+
end
|
|
292
|
+
qt_version = File.read(File.join(build, "qt-version.txt")).strip
|
|
293
|
+
unless qt_version == expected_qt_version
|
|
294
|
+
raise ArgumentError,
|
|
295
|
+
"Qt compiler mismatch: qtpaths reported #{expected_qt_version}, CMake found #{qt_version}"
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
compiled_root = File.join(@path, COMPILED_ROOT)
|
|
299
|
+
FileUtils.remove_entry(compiled_root) if File.directory?(compiled_root)
|
|
300
|
+
destination = File.join(@path, module_path)
|
|
301
|
+
FileUtils.mkdir_p(destination)
|
|
302
|
+
artifact_names.each { |name| FileUtils.cp(File.join(built_module, name), destination) }
|
|
303
|
+
qmldir = File.join(destination, "qmldir")
|
|
304
|
+
File.write(qmldir, File.read(qmldir).rstrip + "\n")
|
|
305
|
+
|
|
306
|
+
SOURCE_ENTRIES.each { |entry| remove_generated(File.join(@path, entry)) }
|
|
307
|
+
ENTRY_TYPES.each do |entry, type|
|
|
308
|
+
File.write(File.join(@path, entry), entry_shim(module_path, type, uri:))
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
artifact_paths = artifact_names.filter_map do |name|
|
|
312
|
+
next if name == "qmldir"
|
|
313
|
+
|
|
314
|
+
relative = File.join(module_path, name)
|
|
315
|
+
absolute = File.join(@path, relative)
|
|
316
|
+
{
|
|
317
|
+
"path" => relative,
|
|
318
|
+
"sha256" => Digest::SHA256.file(absolute).hexdigest,
|
|
319
|
+
"bytes" => File.size(absolute)
|
|
320
|
+
}
|
|
321
|
+
end
|
|
322
|
+
report = {
|
|
323
|
+
"format" => "qt-aot-qml-module",
|
|
324
|
+
"format_version" => FORMAT_VERSION,
|
|
325
|
+
"qt_version" => qt_version,
|
|
326
|
+
"module_uri" => uri,
|
|
327
|
+
"module_path" => module_path,
|
|
328
|
+
"source_fingerprint" => fingerprint,
|
|
329
|
+
"source_files" => qml_files.length,
|
|
330
|
+
"source_bytes" => (qml_files + resources).sum { |file| File.size(File.join(source, file)) },
|
|
331
|
+
"entry_shims" => ENTRY_TYPES.keys,
|
|
332
|
+
"artifacts" => artifact_paths
|
|
333
|
+
}
|
|
334
|
+
File.write(File.join(@path, REPORT), JSON.pretty_generate(report) + "\n")
|
|
335
|
+
checksums = artifact_paths.map { |artifact| "#{artifact.fetch('sha256')} #{artifact.fetch('path')}" }
|
|
336
|
+
File.write(File.join(@path, CHECKSUM), checksums.join("\n") + "\n")
|
|
337
|
+
File.write(File.join(@path, PROVENANCE), provenance(report))
|
|
338
|
+
report
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def remove_generated(path)
|
|
342
|
+
File.directory?(path) ? FileUtils.remove_entry(path) : FileUtils.rm_f(path)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def entry_shim(module_path, type, uri:)
|
|
346
|
+
import = type == "App" ? uri : %Q("#{module_path}")
|
|
347
|
+
<<~QML
|
|
348
|
+
import QtQuick
|
|
349
|
+
import #{import} as Compiled
|
|
350
|
+
|
|
351
|
+
Compiled.#{type} {}
|
|
352
|
+
QML
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def provenance(report)
|
|
356
|
+
artifacts = report.fetch("artifacts").map do |artifact|
|
|
357
|
+
"- `#{artifact.fetch('path')}` — `#{artifact.fetch('sha256')}`"
|
|
358
|
+
end.join("\n")
|
|
359
|
+
<<~MARKDOWN
|
|
360
|
+
# Compiled QML provenance
|
|
361
|
+
|
|
362
|
+
Omarchy UI generated this package's native Qt module from the tree-shaken Zui and
|
|
363
|
+
Omarchy host QML graph. Generated QML source contents were discarded after AOT compilation.
|
|
364
|
+
|
|
365
|
+
- Format: `#{report.fetch('format')}` version #{report.fetch('format_version')}
|
|
366
|
+
- Qt: `#{report.fetch('qt_version')}`
|
|
367
|
+
- Module: `#{report.fetch('module_uri')}`
|
|
368
|
+
- Source fingerprint: `#{report.fetch('source_fingerprint')}`
|
|
369
|
+
|
|
370
|
+
## Artifacts
|
|
371
|
+
|
|
372
|
+
#{artifacts}
|
|
373
|
+
|
|
374
|
+
Verify the packaged libraries from the plugin directory:
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
sha256sum --check #{CHECKSUM}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
`App.qml`, `Service.qml`, `Panel.qml`, and `BarWidget.qml` are the minimal loader shims
|
|
381
|
+
required by Omarchy's file-based entry-point contract. Application UI lives in the compiled
|
|
382
|
+
module recorded by `#{REPORT}`.
|
|
383
|
+
MARKDOWN
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|
data/lib/omarchy_ui/runtime.rb
CHANGED
|
@@ -16,8 +16,14 @@ module OmarchyUI
|
|
|
16
16
|
AUDIT_FILES = %w[
|
|
17
17
|
omarchy-ui-runtime.sha256 runtime-provenance.json RUNTIME_PROVENANCE.md
|
|
18
18
|
].freeze
|
|
19
|
-
GENERATED_ENTRIES = (ADAPTER_FILES + ZUI_GENERATED_ENTRIES +
|
|
20
|
-
|
|
19
|
+
GENERATED_ENTRIES = (ADAPTER_FILES + ZUI_GENERATED_ENTRIES + [
|
|
20
|
+
TREE_SHAKE_REPORT,
|
|
21
|
+
QmlCompiler::COMPILED_ROOT,
|
|
22
|
+
QmlCompiler::REPORT,
|
|
23
|
+
QmlCompiler::CHECKSUM,
|
|
24
|
+
QmlCompiler::PROVENANCE,
|
|
25
|
+
"omarchy-ui-runtime"
|
|
26
|
+
]).freeze
|
|
21
27
|
|
|
22
28
|
def executable
|
|
23
29
|
override = ENV["OMARCHY_UI_RUNTIME"]
|
|
@@ -39,7 +45,7 @@ module OmarchyUI
|
|
|
39
45
|
FileUtils.rm_f(temporary) if temporary && File.exist?(temporary)
|
|
40
46
|
end
|
|
41
47
|
|
|
42
|
-
def install_package(path, framework_root: FRAMEWORK_ROOT, project: path)
|
|
48
|
+
def install_package(path, framework_root: FRAMEWORK_ROOT, project: path, compiled: false)
|
|
43
49
|
FileUtils.mkdir_p(path)
|
|
44
50
|
GENERATED_ENTRIES.each do |entry|
|
|
45
51
|
generated = File.join(path, entry)
|
|
@@ -67,6 +73,7 @@ module OmarchyUI
|
|
|
67
73
|
source = File.join(framework_root, "vendor", "runtime", "x86_64-linux", file)
|
|
68
74
|
FileUtils.cp(source, File.join(path, file)) if File.file?(source)
|
|
69
75
|
end
|
|
76
|
+
QmlCompiler.compile!(path) if compiled
|
|
70
77
|
path
|
|
71
78
|
end
|
|
72
79
|
|
data/lib/omarchy_ui.rb
CHANGED
|
@@ -11,7 +11,7 @@ rescue LoadError
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
module OmarchyUI
|
|
14
|
-
VERSION = "0.0.
|
|
14
|
+
VERSION = "0.0.9"
|
|
15
15
|
ZUI_VERSION = Zui::VERSION
|
|
16
16
|
FRAMEWORK_ROOT = File.expand_path("..", __dir__)
|
|
17
17
|
|
|
@@ -126,5 +126,7 @@ module Zui
|
|
|
126
126
|
end
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
+
require_relative "omarchy_ui/project_config"
|
|
129
130
|
require_relative "omarchy_ui/generator"
|
|
131
|
+
require_relative "omarchy_ui/qml_compiler"
|
|
130
132
|
require_relative "omarchy_ui/runtime"
|
|
@@ -17,4 +17,4 @@ The workflow checks out every external input at a full commit digest, performs t
|
|
|
17
17
|
builds, requires byte-identical executables, and signs the resulting SHA through GitHub's
|
|
18
18
|
artifact attestation service. Machine-readable provenance is included in
|
|
19
19
|
`runtime-provenance.json`; independent verification instructions are in
|
|
20
|
-
[
|
|
20
|
+
the [runtime build documentation](https://github.com/AdamMusa/omarchy-ui/blob/main/docs/runtime-build.md).
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: omarchy-ui
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.9
|
|
5
5
|
platform: x86_64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Adam Moussa Ali
|
|
@@ -39,6 +39,8 @@ files:
|
|
|
39
39
|
- lib/omarchy_ui.rb
|
|
40
40
|
- lib/omarchy_ui/cli.rb
|
|
41
41
|
- lib/omarchy_ui/generator.rb
|
|
42
|
+
- lib/omarchy_ui/project_config.rb
|
|
43
|
+
- lib/omarchy_ui/qml_compiler.rb
|
|
42
44
|
- lib/omarchy_ui/runtime.rb
|
|
43
45
|
- vendor/runtime/x86_64-linux/RUNTIME_PROVENANCE.md
|
|
44
46
|
- vendor/runtime/x86_64-linux/omarchy-ui-runtime
|