@omega.js/desktop 0.53.0 → 0.54.1
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 +38 -38
- package/dist/cli-run.js +4 -1
- package/dist/cli.js +2 -2
- package/dist/commands/cdp/client.js +1 -1
- package/dist/commands/cdp.js +1 -1
- package/dist/commands/clean.js +2 -3
- package/dist/commands/dev.js +25 -0
- package/dist/commands/lib/ensure-target.js +12 -17
- package/dist/commands/lib/migrate.js +17 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/release.js +1 -1
- package/dist/commands/test.js +4 -4
- package/dist/commands/update.js +5 -4
- package/dist/defaults/.github/workflows/build.yml +18 -18
- package/dist/defaults/_.gitignore +0 -2
- package/dist/defaults/_mas/README.md +3 -3
- package/dist/defaults/config/certs/README.md +1 -1
- package/dist/defaults/config/omega.json5 +36 -36
- package/dist/defaults/docs/README.md +3 -3
- package/dist/defaults/gulpfile.js +1 -1
- package/dist/defaults/hooks/build/post.js +1 -1
- package/dist/defaults/hooks/build/pre.js +1 -1
- package/dist/defaults/hooks/notarize/post.js +2 -2
- package/dist/defaults/hooks/release/post.js +1 -1
- package/dist/defaults/hooks/release/pre.js +1 -1
- package/dist/defaults/src/assets/scss/pages/about.scss +1 -1
- package/dist/defaults/src/assets/scss/pages/main.scss +1 -1
- package/dist/defaults/src/assets/scss/pages/settings.scss +1 -1
- package/dist/defaults/src/integrations/context-menu/index.js +11 -11
- package/dist/defaults/src/integrations/menu/index.js +5 -5
- package/dist/defaults/src/integrations/tray/index.js +9 -9
- package/dist/defaults/src/main.js +2 -2
- package/dist/defaults/src/preload.js +1 -1
- package/dist/defaults/test/README.md +3 -3
- package/dist/defaults/test/_init.js +1 -1
- package/dist/gulp/tasks/audit.js +5 -8
- package/dist/lib/restart-manager/index.js +1 -1
- package/dist/lib/restart-manager/install.js +1 -1
- package/dist/lib/restart-manager/protocol.js +1 -1
- package/dist/main.js +4 -3
- package/dist/preload.js +1 -1
- package/dist/test/suites/build/audit.test.js +20 -7
- package/dist/test/suites/build/build-workflow-jobs.test.js +2 -2
- package/dist/test/suites/build/cli.test.js +28 -0
- package/dist/test/suites/build/defaults-em-dash.test.js +22 -0
- package/dist/test/suites/build/defaults-scaffold.test.js +19 -5
- package/dist/test/suites/build/deploy-direct.test.js +7 -5
- package/dist/test/suites/build/deploy-dispatch.test.js +2 -1
- package/dist/test/suites/build/deploy-hook.test.js +4 -2
- package/dist/test/suites/build/dev-verb.test.js +67 -0
- package/dist/test/suites/build/ensure-target.test.js +11 -3
- package/dist/test/suites/build/merge-line-files.test.js +6 -6
- package/dist/test/suites/build/migrate.test.js +29 -0
- package/dist/test/suites/build/project-scripts-deps.test.js +6 -10
- package/dist/test/suites/build/runner-env-write.test.js +73 -0
- package/dist/test/suites/build/runner.test.js +9 -8
- package/dist/test/suites/build/setup-scripts.test.js +27 -0
- package/dist/test/suites/build/validate-config.test.js +13 -2
- package/dist/test/suites/build/verb-logs.test.js +20 -0
- package/dist/test/suites/renderer/window-desktop-surface.test.js +1 -1
- package/dist/utils/build-pipeline.js +4 -4
- package/dist/utils/runner-env.js +13 -28
- package/dist/vendor/config/company.js +46 -14
- package/dist/vendor/config/defaults.js +30 -7
- package/dist/vendor/config/edit.js +25 -3
- package/dist/vendor/config/env-delivery.js +1 -1
- package/dist/vendor/config/env-schema.js +3 -6
- package/dist/vendor/config/env.js +34 -22
- package/dist/vendor/config/index.js +13 -17
- package/dist/vendor/config/load.js +15 -7
- package/dist/vendor/config/repo.js +10 -27
- package/dist/vendor/config/schema-client.js +64 -0
- package/dist/vendor/config/schema-cloud.js +38 -0
- package/dist/vendor/config/schema-manager.js +118 -0
- package/dist/vendor/config/schema-overrides.js +68 -0
- package/dist/vendor/config/schema.js +99 -152
- package/dist/vendor/config/validate.js +97 -77
- package/dist/vendor/devkit/agents-md.js +233 -0
- package/dist/vendor/devkit/attach-log-file.js +15 -1
- package/dist/vendor/devkit/ci-workflows.js +30 -30
- package/dist/vendor/devkit/cli-router.js +13 -7
- package/dist/vendor/devkit/defaults-engine.js +9 -43
- package/dist/vendor/devkit/deploy-snapshot.js +44 -9
- package/dist/vendor/devkit/env-lines.js +183 -0
- package/dist/vendor/devkit/local.js +62 -10
- package/dist/vendor/devkit/lockfile.js +32 -13
- package/dist/vendor/devkit/logger.js +7 -2
- package/dist/vendor/devkit/merge-line-files.js +219 -176
- package/dist/vendor/devkit/omega-bin.js +208 -111
- package/dist/vendor/devkit/preludes/docs-sync.js +52 -0
- package/dist/vendor/devkit/preludes/index.js +1 -0
- package/dist/vendor/devkit/target-picker.js +45 -0
- package/dist/vendor/devkit/test/dashed-files.js +37 -0
- package/dist/vendor/devkit/test/run-verb-under-tee.js +71 -0
- package/dist/vendor/devkit/update.js +15 -15
- package/dist/vendor/devkit/verb-scripts.js +40 -0
- package/dist/vendor/devkit/verbs.js +170 -0
- package/package.json +18 -24
- package/dist/commands/install.js +0 -37
- package/dist/defaults/AGENTS.md +0 -119
- package/dist/defaults/CLAUDE.md +0 -1
- package/dist/vendor/config/env-retired.js +0 -137
- package/dist/vendor/config/retired-keys.js +0 -635
- package/docs/analytics.md +0 -140
- package/docs/app-state.md +0 -92
- package/docs/audit.md +0 -69
- package/docs/auth.md +0 -284
- package/docs/auto-updater.md +0 -243
- package/docs/boot-sequence.md +0 -44
- package/docs/build-system.md +0 -169
- package/docs/cdp-debugging.md +0 -169
- package/docs/common-mistakes.md +0 -21
- package/docs/config-schema.md +0 -120
- package/docs/context-menu.md +0 -112
- package/docs/context.md +0 -81
- package/docs/css.md +0 -84
- package/docs/deep-link.md +0 -186
- package/docs/environment-detection.md +0 -112
- package/docs/fontawesome.md +0 -109
- package/docs/hooks.md +0 -89
- package/docs/icons.md +0 -79
- package/docs/index.md +0 -328
- package/docs/installer-options.md +0 -165
- package/docs/ipc.md +0 -61
- package/docs/lib-modules.md +0 -53
- package/docs/logging.md +0 -227
- package/docs/menu.md +0 -160
- package/docs/releasing.md +0 -239
- package/docs/remote-config.md +0 -118
- package/docs/remote-scripts.md +0 -144
- package/docs/restart-manager.md +0 -144
- package/docs/runner.md +0 -290
- package/docs/sentry.md +0 -97
- package/docs/shared/agent-docs.md +0 -89
- package/docs/shared/analytics.md +0 -612
- package/docs/shared/brands.md +0 -57
- package/docs/shared/breaking-changes.md +0 -917
- package/docs/shared/config.md +0 -1948
- package/docs/shared/deploys.md +0 -341
- package/docs/shared/icons.md +0 -219
- package/docs/shared/local-dev.md +0 -167
- package/docs/shared/logging.md +0 -205
- package/docs/shared/monitoring.md +0 -167
- package/docs/shared/publishing.md +0 -187
- package/docs/shared/rulings.md +0 -34
- package/docs/shared/testing.md +0 -147
- package/docs/shared/theming.md +0 -629
- package/docs/shared/translation.md +0 -342
- package/docs/shared/updates.md +0 -61
- package/docs/signing.md +0 -293
- package/docs/startup.md +0 -142
- package/docs/storage.md +0 -59
- package/docs/templating.md +0 -101
- package/docs/test-boot-layer.md +0 -157
- package/docs/test-framework.md +0 -362
- package/docs/themes.md +0 -149
- package/docs/tooltips.md +0 -99
- package/docs/tray.md +0 -164
- package/docs/usage.md +0 -58
- package/docs/verts.md +0 -62
- package/docs/windows.md +0 -149
package/docs/auto-updater.md
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
# Auto-updater
|
|
2
|
-
|
|
3
|
-
Wraps `electron-updater` with three triggers: startup check, periodic check, and a 30-day max-age gate that force-installs pending updates that have been ignored too long.
|
|
4
|
-
|
|
5
|
-
## Triggers
|
|
6
|
-
|
|
7
|
-
| Trigger | When | Behavior |
|
|
8
|
-
|---|---|---|
|
|
9
|
-
| **Startup check** | `startupDelayMs` after `app.whenReady()` (default 10s) | Non-blocking. Fires once. |
|
|
10
|
-
| **Feed check** | Every `feedCheckIntervalMs` (default 1h) | HTTP poll of the release feed; also re-evaluates the 30-day gate each tick. |
|
|
11
|
-
| **Idle evaluation** | Every `idleEvalIntervalMs` (default 60s) | Cheap in-process check: install a downloaded update once the user has been idle long enough. |
|
|
12
|
-
| **30-day gate** | When a download lands + every feed tick | If a pending update was downloaded ≥ `maxAgeMs` ago (default 30 days), force `quitAndInstall()`. A pending update carried from a prior session keeps its original `downloadedAt`, so the gate trips as soon as the startup check re-downloads it. |
|
|
13
|
-
| **Manual check** | `omega.autoUpdater.checkNow()` (main) or `window.desktop.autoUpdater.checkNow()` (renderer) | Same as a periodic check but `userInitiated: true`. |
|
|
14
|
-
|
|
15
|
-
## State machine
|
|
16
|
-
|
|
17
|
-
`status.code` is one of:
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
idle — nothing happening
|
|
21
|
-
checking — checking the feed
|
|
22
|
-
available — feed says an update exists
|
|
23
|
-
downloading — download in progress (status.percent updated)
|
|
24
|
-
downloaded — fully downloaded; ready to install. Also: pendingUpdate.downloadedAt is set in storage.
|
|
25
|
-
not-available — feed says no update
|
|
26
|
-
error — checkForUpdates() or download failed; status.error.message has details
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Config (`config/omega.json5`)
|
|
30
|
-
|
|
31
|
-
```jsonc
|
|
32
|
-
autoUpdate: {
|
|
33
|
-
enabled: true,
|
|
34
|
-
channel: 'latest', // latest / beta / alpha
|
|
35
|
-
startupDelayMs: 10000, // 10s after whenReady
|
|
36
|
-
feedCheckIntervalMs: 3600000, // 1h — release-feed poll cadence (HTTP; also re-checks the 30-day gate)
|
|
37
|
-
idleEvalIntervalMs: 60000, // 60s — idle-install evaluator cadence (in-process)
|
|
38
|
-
maxAgeMs: 2592000000, // 30 days; if pendingUpdate is older, force install
|
|
39
|
-
autoDownload: true, // electron-updater downloads automatically
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## 30-day max-age gate
|
|
44
|
-
|
|
45
|
-
The problem: if a user keeps their app open for weeks, an update may download but never apply. Eventually their version is dangerously stale (e.g. an unpatched security issue).
|
|
46
|
-
|
|
47
|
-
The gate:
|
|
48
|
-
|
|
49
|
-
1. **First download wins.** When `update-downloaded` fires, @omega.js/desktop stores `pendingUpdate = { version, downloadedAt: Date.now() }` to `storage.autoUpdater.pendingUpdate`.
|
|
50
|
-
2. **Subsequent downloads do NOT reset the timer.** If a newer update downloads later, `downloadedAt` stays at the original time. (Otherwise the user could keep dodging by triggering re-checks.)
|
|
51
|
-
3. **When a download lands + every feed tick**, @omega.js/desktop checks if `Date.now() - downloadedAt >= maxAgeMs`. If yes → `quitAndInstall()`. Force. (At init the artifact isn't re-downloaded yet — the restored `downloadedAt` makes the gate trip the moment the startup check's download completes.)
|
|
52
|
-
4. **Cleared on apply.** When the app next launches and `app.getVersion() === pendingUpdate.version`, the flag is cleared automatically (the user successfully restarted into the new version).
|
|
53
|
-
|
|
54
|
-
This guarantees no app on @omega.js/desktop stays > maxAgeMs days behind a downloaded update — provided `autoDownload` stays on (default): the cross-session gate enforces when the startup check's re-download lands, so with `autoDownload: false` it waits for the next download, whenever the consumer triggers one.
|
|
55
|
-
|
|
56
|
-
## Idle-aware install (15-min default)
|
|
57
|
-
|
|
58
|
-
When an update finishes downloading via a background poll (NOT a user-initiated check), @omega.js/desktop does NOT immediately quit-and-install. Two independent timers own the decision: the feed-check timer (`feedCheckIntervalMs`, HTTP, also enforces the 30-day gate) and the idle-eval timer (`idleEvalIntervalMs`, cheap in-process arithmetic) which installs the downloaded update once the user has been idle past the threshold. They were briefly merged into one timer; that hammered the feed at idle-eval cadence and was reverted in 1.3.1.
|
|
59
|
-
|
|
60
|
-
### Activity signals
|
|
61
|
-
|
|
62
|
-
Any UI activity bumps `_lastActivityAt = Date.now()`. Built-in signals:
|
|
63
|
-
|
|
64
|
-
- **Renderer-side** — `mousedown`, `keydown`, `wheel`, `touchstart`, `focus` on `window` (capture phase, debounced to once per 5s in preload; sent to main as IPC `desktop:auto-updater:activity` which routes through `_onActivityIpc → markActive`).
|
|
65
|
-
- **Main-side** — `app.on('browser-window-focus')` (covers tray-click-to-show, dock click, alt-tab back, etc.) wired during `_wireActivityHooks()` (idempotent, one-shot per process).
|
|
66
|
-
|
|
67
|
-
### Decision flow (`_evaluateIdleInstall`)
|
|
68
|
-
|
|
69
|
-
Runs every periodic tick. Conditions, in order:
|
|
70
|
-
|
|
71
|
-
- If `state.code !== 'downloaded'` → no-op (nothing to install).
|
|
72
|
-
- If `_userInitiated` → no-op (consumer UI owns the install affordance).
|
|
73
|
-
- If `_isDevMode()` → no-op (dev simulator's `quitAndInstall` is a no-op anyway).
|
|
74
|
-
- If `Date.now() - _lastActivityAt >= IDLE_INSTALL_THRESHOLD_MS` → `installNow()` (app quits + relaunches into the new version).
|
|
75
|
-
- Else if `_promptedForVersion !== state.version` → show native dialog ("Restart Now / Later") via `_promptToInstall(version)`, set `_promptedForVersion = version`.
|
|
76
|
-
- Else → no-op this tick. Try again next tick.
|
|
77
|
-
|
|
78
|
-
Constants hardcoded at the top of `src/lib/auto-updater.js`:
|
|
79
|
-
- `IDLE_INSTALL_THRESHOLD_MS = 15 * 60 * 1000` — how long the user must be idle.
|
|
80
|
-
|
|
81
|
-
### User-initiated checks bypass everything
|
|
82
|
-
|
|
83
|
-
When someone clicks "Check for Updates" in the menu/tray, `checkNow({userInitiated: true})` fires. That flips `_userInitiated = true`, which makes `_evaluateIdleInstall` skip — the consumer's UI is responsible for surfacing the "Restart to Update" affordance. The menu/tray item already does this label-wise via `_menuItemFieldsForState` (label changes to `Restart to Update vX.Y.Z` + enabled).
|
|
84
|
-
|
|
85
|
-
### `checkNow()` dedup + `_userInitiated` leak fix
|
|
86
|
-
|
|
87
|
-
`_readyToCheck()` returns true only when `state.code` is `idle | not-available | error`. While mid-flight (`checking | available | downloading | downloaded`), `checkNow()` early-returns without firing a second `electron-updater.checkForUpdates()`. Combined with Electron's `ipcMain.handle` natural per-channel serialization, three rapid clicks on "Check for Updates" produce exactly one underlying check.
|
|
88
|
-
|
|
89
|
-
Subtle: `_userInitiated` is only flipped AFTER the `_readyToCheck` guard. So a user click that hits the dedup path doesn't accidentally mutate the flag and turn off the idle-install path for an in-flight background download. (Pre-1.2.39 had this bug.)
|
|
90
|
-
|
|
91
|
-
### Consumer hook: `markActive()`
|
|
92
|
-
|
|
93
|
-
Consumers can force-bump the activity timestamp from anywhere:
|
|
94
|
-
|
|
95
|
-
```js
|
|
96
|
-
omega.autoUpdater.markActive();
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Call this from app-specific signals the framework can't see — e.g. just received an auth event, finished a long renderer task, finished a backend sync. Use sparingly; the built-in renderer mouse/keyboard/focus signals cover almost everything.
|
|
100
|
-
|
|
101
|
-
### Why 15 minutes?
|
|
102
|
-
|
|
103
|
-
Long enough that an actively-used app won't surprise-quit mid-task. Short enough that a user who minimizes the app and walks to lunch comes back to the new version. Tune `IDLE_INSTALL_THRESHOLD_MS` if your app's usage pattern is different.
|
|
104
|
-
|
|
105
|
-
### Implementation notes
|
|
106
|
-
|
|
107
|
-
- `_promptedForVersion` tracks the version we've already shown the dialog for. Reset on `shutdown()`. If a *newer* update downloads later, the version flips and the prompt fires again (different version).
|
|
108
|
-
- The first `_evaluateIdleInstall` after a download lands waits up to one tick (default 60s) before any prompt or install — gives an active user a small grace window to reach a natural pause before the dialog appears.
|
|
109
|
-
- The 30-day gate firing short-circuits idle eval: `_feedCheckTick` returns after `_enforceMaxAgeGate()` returns true, since the install is already in flight.
|
|
110
|
-
|
|
111
|
-
### Test mode behavior
|
|
112
|
-
|
|
113
|
-
When `omega.isTesting() === true` (the one input: `OMEGA_ENVIRONMENT=testing`), the auto-updater swaps in test-friendly defaults so a real download → idle wait → install can complete in seconds instead of minutes:
|
|
114
|
-
|
|
115
|
-
- **Idle threshold**: `IDLE_INSTALL_THRESHOLD_MS_TESTING = 3000ms` (3 sec) instead of 15 min.
|
|
116
|
-
- **Both timers**: `IDLE_TICK_MS_TESTING = 500ms` replaces `feedCheckIntervalMs` and `idleEvalIntervalMs`.
|
|
117
|
-
- **`_promptToInstall` short-circuits** before invoking `dialog.showMessageBox`. The native dialog is modal + blocking + would pop a window the test process can't dismiss programmatically. In test mode the prompt logs `[testing] _promptToInstall(...) — skipped native dialog.` and returns. Tests that want to assert prompt behavior override `_promptToInstall` per-test (see `auto-updater.test.js`).
|
|
118
|
-
|
|
119
|
-
This lets the framework's own integration tests drive the full sequence (`OMEGA_DEV_UPDATE=available` → state machine → 500ms tick → 3s idle threshold elapses → stubbed `installNow` fires) in ~5s. Consumers running their own tests should name `OMEGA_ENVIRONMENT=testing` to inherit the same defaults.
|
|
120
|
-
|
|
121
|
-
## Menu integration
|
|
122
|
-
|
|
123
|
-
@omega.js/desktop's default menu template includes a "Check for Updates..." item with id `desktop:check-for-updates`. The auto-updater listens to its own status changes and updates the item's label + enabled state, VS Code-style:
|
|
124
|
-
|
|
125
|
-
| State | Label | Enabled |
|
|
126
|
-
|---|---|---|
|
|
127
|
-
| `idle` / `error` | Check for Updates... | yes |
|
|
128
|
-
| `checking` | Checking for Updates... | no |
|
|
129
|
-
| `available` | Downloading Update v{version}... | no |
|
|
130
|
-
| `downloading` | Downloading Update ({percent}%) | no |
|
|
131
|
-
| `downloaded` | Restart to Update v{version} | yes (clicks `installNow()`) |
|
|
132
|
-
| `not-available` | You're up to date | yes |
|
|
133
|
-
|
|
134
|
-
Click handler defaults to `checkNow()` when not yet downloaded; `installNow()` when downloaded.
|
|
135
|
-
|
|
136
|
-
Consumers can find / move / remove the item via `omega.menu.findItem('desktop:check-for-updates')` etc.: see [docs/menu.md](menu.md).
|
|
137
|
-
|
|
138
|
-
## Renderer surface
|
|
139
|
-
|
|
140
|
-
Preload exposes `window.desktop.autoUpdater`:
|
|
141
|
-
|
|
142
|
-
```js
|
|
143
|
-
// Get current state
|
|
144
|
-
const status = await window.desktop.autoUpdater.getStatus();
|
|
145
|
-
// → { code, version, percent, error, downloadedAt, lastCheckedAt }
|
|
146
|
-
|
|
147
|
-
// Subscribe to updates
|
|
148
|
-
const unsubscribe = window.desktop.autoUpdater.onStatus((status) => {
|
|
149
|
-
console.log('update status →', status.code, status.version, status.percent);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// User-initiated check (e.g. "Check for updates" menu item)
|
|
153
|
-
await window.desktop.autoUpdater.checkNow();
|
|
154
|
-
|
|
155
|
-
// User-initiated install (after status === 'downloaded')
|
|
156
|
-
await window.desktop.autoUpdater.installNow();
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
Status is also broadcast on the IPC channel `desktop:auto-updater:status` after every state transition.
|
|
160
|
-
|
|
161
|
-
## Dev simulation
|
|
162
|
-
|
|
163
|
-
Without a real update server you can validate the entire flow via env vars:
|
|
164
|
-
|
|
165
|
-
```bash
|
|
166
|
-
# Simulate "update available" — full cascade through downloading → downloaded
|
|
167
|
-
OMEGA_DEV_UPDATE=available npm start
|
|
168
|
-
|
|
169
|
-
# Simulate "no update available" — lands in not-available
|
|
170
|
-
OMEGA_DEV_UPDATE=unavailable npm start
|
|
171
|
-
|
|
172
|
-
# Simulate a feed failure — lands in error
|
|
173
|
-
OMEGA_DEV_UPDATE=error npm start
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
In dev simulation mode, `quitAndInstall()` is a no-op (no actual restart) so you can step through the dialog flow without the app exiting.
|
|
177
|
-
|
|
178
|
-
The simulated download is memory-only: it never writes the `pendingUpdate` storage record, and the reconciler discards a stored one carrying the simulator's reserved `999.0.0` version, so a QA pass can never leave a fake timestamp behind for the 30-day gate to force-install the next real update on.
|
|
179
|
-
|
|
180
|
-
### The menu trigger
|
|
181
|
-
|
|
182
|
-
Relaunching once per scenario is a slow way to walk three outcomes, so the default menu carries the same cascade on demand. In development, **View → Developer → Simulate update** lists one item per scenario:
|
|
183
|
-
|
|
184
|
-
| Item | ID | Outcome |
|
|
185
|
-
|---|---|---|
|
|
186
|
-
| Update available | `view/developer/simulate-update/available` | full cascade through downloading → downloaded |
|
|
187
|
-
| No update available | `view/developer/simulate-update/unavailable` | lands in `not-available` |
|
|
188
|
-
| Update error | `view/developer/simulate-update/error` | lands in `error` |
|
|
189
|
-
|
|
190
|
-
Each item calls `omega.autoUpdater.simulate(scenario)`, which is callable from anywhere in main:
|
|
191
|
-
|
|
192
|
-
```js
|
|
193
|
-
await omega.autoUpdater.simulate('available');
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
Rules of the road:
|
|
197
|
-
|
|
198
|
-
- It throws with `scenario required` when called with no argument, and on an unknown scenario (`available`, `unavailable`, `error` are the whole set, shared with the env var).
|
|
199
|
-
- It **refuses in a packaged production build** unless that build was launched with `OMEGA_DEV_UPDATE` set. Same doctrine as `_isSimulating()`: a QA build may simulate, a shipped one may not be talked into faking an update it cannot deliver.
|
|
200
|
-
- It **refuses while a cascade is still running** (one simulation at a time). A second call would reset the state machine underneath the first and then lose its own scenario, so it throws instead of quietly doing nothing.
|
|
201
|
-
- It resolves with the status once the cascade is running (like `checkNow()`); the terminal state arrives over the usual `desktop:auto-updater:status` broadcast a few hundred ms later.
|
|
202
|
-
- The first call swaps the real `electron-updater` instance out for the synthetic one and **leaves it swapped for the rest of the session**. Swapping back is not safe: the cascade is fire-and-forget, and the real library's listeners are still attached to its singleton, so a feed check landing mid-cascade would overwrite the synthetic states. Relaunch to get the real updater back.
|
|
203
|
-
|
|
204
|
-
### The session latch
|
|
205
|
-
|
|
206
|
-
Because the synthetic library stays wired, every LATER trigger drives it too: the hourly feed-check tick calls `checkForUpdates()` with no scenario, the simulator falls back to `available`, and the state machine lands on a fake `downloaded v999.0.0`. So the first `simulate()` also latches the process into simulation mode, and `_isSimulating()` reads:
|
|
207
|
-
|
|
208
|
-
```js
|
|
209
|
-
!!process.env.OMEGA_DEV_UPDATE || _devSimulationSession
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
That latch is what keeps a synthetic update out of the real install path. Every existing guard is written against `_isSimulating()`, so with it set:
|
|
213
|
-
|
|
214
|
-
- `_evaluateIdleInstall()` bails, so no native "restart to update" prompt fires for an update that does not exist.
|
|
215
|
-
- `installNow()` bails before `omega._allowQuit = true` and `quitAndInstall()`.
|
|
216
|
-
|
|
217
|
-
Without the latch, a plain dev session (env var unset) that clicked the menu once would hit all of the above on the next tick. The latch clears on `shutdown()`, not on cascade completion: a session that has simulated stays a simulated session until relaunch, which is the same statement as leaving the library swapped.
|
|
218
|
-
|
|
219
|
-
The submenu is dev-only (same gate as `view/developer/toggle-devtools`) and is an ordinary menu item, so `omega.menu.remove('view/developer/simulate-update')` drops it like any other.
|
|
220
|
-
|
|
221
|
-
## Production: how electron-updater finds the feed
|
|
222
|
-
|
|
223
|
-
`electron-updater` reads the `publish` block from the embedded `app-update.yml` (baked into the `.app` / `.exe` at build time by electron-builder). @omega.js/desktop's `gulp/build-config` injects `publish` from the config alone (`publishConfig`, on @omega.js/config's `releasesRepo`) into `dist/electron-builder.yml` before packaging, so the published `app-update.yml` points at:
|
|
224
|
-
|
|
225
|
-
```
|
|
226
|
-
provider: github
|
|
227
|
-
owner: <repo.org>
|
|
228
|
-
repo: `${brand.id}-releases`
|
|
229
|
-
releaseType: release
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
So your private app repo and your public release repo are completely decoupled — the bundled binary knows where to look for updates. The address is never guessed from a git remote ([#799](https://github.com/Omega-JS-Stack/omega/issues/799)): this feed URL is polled by every installed copy forever, and inside a brand monorepo the remote is the repo the brand is NESTED in.
|
|
233
|
-
|
|
234
|
-
## Failure modes
|
|
235
|
-
|
|
236
|
-
- **Update repo isn't public** → `electron-updater` gets 404 or 401 against a private repo. Fix: ensure the releases repo (`<repo.org>/<brand.id>-releases`) is public; `omega deploy`'s precheck creates it that way.
|
|
237
|
-
- **Token rotates / expires for `releases` repo** — `electron-updater` doesn't authenticate downloads (anonymous public reads). So tokens don't apply on the consumer side.
|
|
238
|
-
- **`app-update.yml` missing in the packaged app** → look at the build log for `electron-builder`'s "creating updates yml" line. If it's skipped, your `publish` block didn't materialize correctly into `dist/electron-builder.yml`.
|
|
239
|
-
- **`error` status with code `ERR_UPDATER_CHANNEL_FILE_NOT_FOUND`** → there's no `latest-mac.yml` (or `latest.yml` / `latest-linux.yml`) at the configured channel. Run a release first.
|
|
240
|
-
|
|
241
|
-
## Tests
|
|
242
|
-
|
|
243
|
-
- `src/test/suites/main/auto-updater.test.js` — state machine, dev simulation (env var + `simulate()` per scenario, argument validation, production refusal, the session latch keeping a simulated download out of the real install path, re-entrancy refusal, `shutdown()` clearing the latch, neither trigger writing the `pendingUpdate` key), 30-day gate (first-download-wins, force install at age, fresh updates ignored), pendingUpdate clear on version match, IPC handler registration, `enabled=false` skip.
|
package/docs/boot-sequence.md
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# Boot Sequence
|
|
2
|
-
|
|
3
|
-
`omega.initialize()` runs in the main process in a fixed order. Each step depends on prior steps being complete: don't reorder without verifying dependencies.
|
|
4
|
-
|
|
5
|
-
## Order
|
|
6
|
-
|
|
7
|
-
1. **`startup.applyEarly()`** — first thing, before `whenReady`. Calls `app.dock.hide()` for `mode: 'hidden'` (zero-bounce production via `LSUIElement` baked at build time).
|
|
8
|
-
1b. **userData path isolation** — appends an environment suffix to `app.getPath('userData')` so each environment's session data, logs, and `electron-store` files stay separate on the same machine: production untouched, development gets ` (Development)`, testing (`OMEGA_ENVIRONMENT=testing`) gets ` (Testing)`. The testing dir is **wiped at boot** so every test run starts from a clean slate (post-run state stays on disk for inspection until the next run; set `OMEGA_TEST_KEEP_USERDATA=1` to skip the wipe). **Must run before `storage.initialize()`** (which constructs `electron-store` against the path).
|
|
9
|
-
1c. **Global user-agent fallback** — sets `app.userAgentFallback` to a branded template via `node-powertools.template`. Default per-platform templates: `Mozilla/5.0 (... <platform-specific> ...) AppleWebKit/537.36 (KHTML, like Gecko) {brand.name}/{app.version} Chrome/{chrome} Safari/537.36`. Merge tags resolve from `{ brand: { name, id }, app: { version }, chrome, electron, node, platform, arch }`. Every BrowserWindow load + electron-updater fetch + node-fetch via the renderer carries the branded UA. Consumers can override post-init by re-setting `app.userAgentFallback` from their main.js.
|
|
10
|
-
2. **`app.on('before-quit')`** wired: sets `omega._isQuitting = true` so any quit path (Cmd+Q, role:'quit' menu, programmatic `app.quit()`, OS shutdown) bypasses the window-manager's hide-on-close trap.
|
|
11
|
-
3. **`ipc`** — typed channel bus online before any feature can register handlers.
|
|
12
|
-
4. **`storage`** — async (electron-store v11 ESM, bundled eagerly into `main.bundle.js` — see [storage.md](storage.md)). Other libs depend on this.
|
|
13
|
-
4b. **`theme`** — sets `nativeTheme.themeSource` from the persisted override (storage `theme.appearance`) → config `theme.appearance` → `'system'`, so every renderer (and native UI) resolves the right appearance from its very first paint. Needs storage + ipc only; must run before any window exists. See [themes.md](themes.md).
|
|
14
|
-
4c. **`fontawesome`**: serves the bundled icon SVGs to renderers over IPC (`desktop:fontawesome:get`). Needs ipc only.
|
|
15
|
-
5. **`sentry`** — earliest catchable global handler.
|
|
16
|
-
6. **`protocol`** — single-instance lock + custom scheme register.
|
|
17
|
-
7. **`deepLink`** — argv parse for cold-start, second-instance handler.
|
|
18
|
-
7b. **`authFlow`**: the sign-in round trip in the user's default browser (`omega.openAuthFlow()`); dev/test return through a loopback listener, since the scheme isn't OS-registered there.
|
|
19
|
-
8. **`appState`** — first-launch / launch-count / crash-sentinel / version-change.
|
|
20
|
-
8b. **`context`**: session id, deviceId, OS info, the async geolocation fetch. After storage (it writes deviceId), before analytics (which reads it).
|
|
21
|
-
8c. **`usage`**: opens / hours-total / hours-this-session, recorded on quit.
|
|
22
|
-
9. `await app.whenReady()`.
|
|
23
|
-
10. **`autoUpdater`** — electron-updater, never blocks.
|
|
24
|
-
11. **`tray`**, **`menu`**, **`contextMenu`**: file-based definitions from `src/integrations/{tray,menu,context-menu}/index.js`. Disable any of them at runtime via `omega.<name>.disable()` (no config flag).
|
|
25
|
-
12. **`startup.initialize`** — applies `setLoginItemSettings`.
|
|
26
|
-
13. **`auth`**: `omega.auth`, the main-side Firebase Auth source of truth, and the IPC handlers every renderer syncs through ([auth.md](auth.md)).
|
|
27
|
-
13b. **`remoteConfig`** — hot config from `<brand.url>/data/resources/main.json`. Non-blocking fire-and-forget fetch.
|
|
28
|
-
13c. **`remoteScripts`** — emergency remote code execution from `<brand.url>/data/scripts/main.js`. Non-blocking. Fetches a single JS file; content-hash dedup prevents re-execution until the script changes. Full main-process access.
|
|
29
|
-
13d. **`analytics`**: GA4 Measurement Protocol. Wired AFTER `auth` so it can subscribe with `omega.auth.listen()`.
|
|
30
|
-
13e. **`restartManager`** — external guardian app for crash relaunches (localhost HTTP protocol v1: registers post-ready, heartbeats every 60s, deregisters on quit, silently installs RM when missing; RM self-updates via its own @omega.js/desktop autoUpdater). See [restart-manager.md](restart-manager.md).
|
|
31
|
-
14. **`windows.initialize`**: registers app-level handlers: `window-all-closed` → quit on win/linux; `app.on('activate')` on macOS to surface `main` when the user double-clicks the dock icon (CleanMyMac-style). **Does NOT auto-create any window.** The consumer's main.js calls `omega.windows.create('main', { show: !startup.isLaunchHidden() })` from inside `omega.initialize().then(() => { ... })`. The `main` window is *always* created (so it's in the registry for the activate/second-instance handlers to find), but `show: false` keeps it invisible in hidden launches: tray icon shows immediately, dock icon + window appear only when something explicitly calls `windows.show('main')` (or the user double-clicks the running app).
|
|
32
|
-
15. **`deepLink.markOmegaReady()`**: releases the deep-link dispatch queue. Cold-start URLs (and any early `open-url`) wait here, so a route like `auth/token` never fires before `omega.auth` has Firebase up.
|
|
33
|
-
|
|
34
|
-
## Why this order
|
|
35
|
-
|
|
36
|
-
- userData path append before storage init: otherwise dev and prod stores share the same path.
|
|
37
|
-
- before-quit before window-manager: so window-manager's close-trap can read `_isQuitting`.
|
|
38
|
-
- IPC before any other lib: every lib registers its own IPC handlers.
|
|
39
|
-
- Storage before sentry: sentry persists scope data.
|
|
40
|
-
- Theme right after storage: the persisted appearance override must hit `nativeTheme.themeSource` before any renderer paints, or the first frame flashes the wrong appearance.
|
|
41
|
-
- protocol/deepLink before whenReady: argv parsing for cold-start deep links must beat first window creation.
|
|
42
|
-
- whenReady gate is where Electron's app APIs become safe.
|
|
43
|
-
- autoUpdater after whenReady but never blocks boot.
|
|
44
|
-
- File-based integrations (tray/menu/context-menu) after everything that wires their click handlers (e.g. autoUpdater patches the check-for-updates menu item).
|
package/docs/build-system.md
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
# Build System
|
|
2
|
-
|
|
3
|
-
@omega.js/desktop's pipeline: **prepare-package** (framework only) → **gulp** (consumer) → **esbuild** (3 bundles) → **electron-builder** (packaging) → **strategy-pluggable signing**.
|
|
4
|
-
|
|
5
|
-
## prepare-package (framework-side)
|
|
6
|
-
|
|
7
|
-
Copies @omega.js/desktop's `src/` → `dist/` so consumers `require('@omega.js/desktop/main')` from the built output. Configured in @omega.js/desktop's `package.json`:
|
|
8
|
-
|
|
9
|
-
```jsonc
|
|
10
|
-
"preparePackage": {
|
|
11
|
-
"input": "./src",
|
|
12
|
-
"output": "./dist",
|
|
13
|
-
"type": "copy",
|
|
14
|
-
"replace": {},
|
|
15
|
-
"hooks": {}
|
|
16
|
-
}
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Run with `npm start` (watch) or `npm run prepare` (one-shot).
|
|
20
|
-
|
|
21
|
-
## Gulp (consumer-side)
|
|
22
|
-
|
|
23
|
-
Auto-loads tasks from `<@omega.js/desktop>/dist/gulp/tasks/*.js` via `<@omega.js/desktop>/dist/gulp/main.js`. Consumer's `package.json` points there:
|
|
24
|
-
|
|
25
|
-
```jsonc
|
|
26
|
-
"scripts": {
|
|
27
|
-
"gulp": "gulp --cwd ./ --gulpfile ./node_modules/@omega.js/desktop/dist/gulp/main.js"
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### Tasks
|
|
32
|
-
|
|
33
|
-
| Task | Status | Description |
|
|
34
|
-
|---|---|---|
|
|
35
|
-
| `defaults` | real | Copy `<@omega.js/desktop>/dist/defaults/*` into the consumer (skips existing files) |
|
|
36
|
-
| `distribute` | real | Stage consumer `src/` + @omega.js/desktop `dist/` into `.desktop-build/` |
|
|
37
|
-
| `bundle` | real | Three parallel bundles — main / preload / renderer, through @omega.js/devkit's `bundle()` wrapper. Named `webpack` until [#737](https://github.com/Omega-JS-Stack/omega/issues/737) |
|
|
38
|
-
| `sass` | real | SCSS → `dist/assets/css/*` |
|
|
39
|
-
| `html` | real | `src/views/**/index.html` → `dist/views/*` |
|
|
40
|
-
| `build-config` | real | Materialize `dist/electron-builder.yml` from source + mode-dependent injections (`LSUIElement` for hidden mode) |
|
|
41
|
-
| `package` | real | Run `electron-builder build --config dist/electron-builder.yml` (full DMG/zip/universal-mac, NSIS-win, deb+AppImage-linux) |
|
|
42
|
-
| `package-quick` | real | Quick-package for host platform/arch only — `--dir` mode, no DMG/zip/universal/notarize. ~30s vs ~3min for full `package`. Output: `release/<platform>-<arch>/<ProductName>.app` (or `.exe`-folder/linux-unpacked) — directly launchable. Used for smoke-testing packaged-mode behavior locally. `--quick` trims the electron-builder phase and NOTHING else: the build ahead of it is full and cold (#737). |
|
|
43
|
-
| `release` | real | `electron-builder build --publish always` |
|
|
44
|
-
| `audit` | real | Validate consumer config (required keys, valid enums, deep-link scheme format), ensure icon + entrypoints exist; in publish mode also requires an ADDRESSABLE releases repo (`repo.org` + `brand.id`) + `electron-builder.yml`. Throws with a numbered list of every problem found |
|
|
45
|
-
| `serve` | real | Spawns `electron .` against the build output, websocket on `OMEGA_LIVERELOAD_PORT` |
|
|
46
|
-
|
|
47
|
-
### Composition
|
|
48
|
-
|
|
49
|
-
```js
|
|
50
|
-
// `build` produces bundles only (dist/main.bundle.js, etc.) — no installer.
|
|
51
|
-
exports.build = series(
|
|
52
|
-
exports['hook:build:pre'],
|
|
53
|
-
exports.defaults,
|
|
54
|
-
exports.distribute,
|
|
55
|
-
parallel(exports.sass, exports.bundle, exports.html),
|
|
56
|
-
exports.audit,
|
|
57
|
-
exports['build-config'],
|
|
58
|
-
exports['hook:build:post'],
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
// `packageBuild` = build + electron-builder (full DMG/zip/universal). Slow (~3min on mac).
|
|
62
|
-
exports.packageBuild = series(exports.build, exports.package);
|
|
63
|
-
|
|
64
|
-
// `packageQuick` = build + electron-builder --dir for host platform/arch only.
|
|
65
|
-
// Fast (~20-30s) — smoke-testing only.
|
|
66
|
-
exports.packageQuick = series(exports.build, exports['package-quick']);
|
|
67
|
-
|
|
68
|
-
// `publish` = build + sign + notarize + GH Release upload (to the brand's ONE
|
|
69
|
-
// public releases repo, under versionless names).
|
|
70
|
-
exports.publish = series(
|
|
71
|
-
exports.build,
|
|
72
|
-
exports['hook:release:pre'],
|
|
73
|
-
exports.release,
|
|
74
|
-
exports['hook:release:post'],
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
exports.default = series(exports.build, exports.serve);
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## esbuild — three bundles
|
|
81
|
-
|
|
82
|
-
All bundled in production for source protection. `app.asar` alone is not obfuscation (anyone can `npx asar extract` it) — minification and name mangling are what protect framework + app source.
|
|
83
|
-
|
|
84
|
-
Every bundle goes through @omega.js/devkit's ONE `bundle()` wrapper ([docs/devkit/index.md](../../../docs/devkit/index.md)), which composes the shared parts: the framework-deps resolve hook (#87), the production `@dev-only` strip (#18), the minify/sourcemap rules by mode, and one timing line per build. `src/gulp/tasks/bundle.js` holds only what is desktop's own.
|
|
85
|
-
|
|
86
|
-
| Bundle | Entry | Output | Platform / format | Externals |
|
|
87
|
-
|---|---|---|---|---|
|
|
88
|
-
| `main` | `src/main.js` | `dist/main.bundle.js` | `node` / `cjs` | electron + node builtins + native modules from consumer's `package.json` |
|
|
89
|
-
| `preload` | `src/preload.js` | `dist/preload.bundle.js` | `node` / `cjs` | electron — `platform: 'node'` already leaves every built-in external, same as main, so electron is the only name worth stating |
|
|
90
|
-
| `renderer` | `src/assets/js/components/<view>/index.js` | `dist/assets/js/components/<view>.bundle.js` | `browser` / `iife` | none — Node built-ins resolve to an empty module (see below) |
|
|
91
|
-
|
|
92
|
-
### Syntax floor — the pinned Electron answers it
|
|
93
|
-
|
|
94
|
-
webpack encoded the runtime as `target: 'electron-main' | 'electron-preload' | 'web'`. esbuild splits it into `platform` (above) and `target` (the syntax floor), and the floor is READ from the Electron binary the consumer pinned: [src/utils/electron-targets.js](../src/utils/electron-targets.js) runs it once per build with `ELECTRON_RUN_AS_NODE` (no window, no focus) and takes `process.versions.node` → `node<version>` for main/preload and `process.versions.chrome` → `chrome<major>` for the renderer. The binary is resolved from the FRAMEWORK's module context — the same lookup `build-config` pins `electronVersion` with, so the bundles compile for the Electron that will actually run them. A binary that can't be run (a CI job with `ELECTRON_SKIP_BINARY_DOWNLOAD`) warns and drops the floor; it never invents a version.
|
|
95
|
-
|
|
96
|
-
### Node built-ins in the renderer
|
|
97
|
-
|
|
98
|
-
The renderer runs with `contextIsolation: true` — a browser-like environment with no Node globals — but libraries bundled through @omega.js/client still IMPORT `fs`, `path`, `crypto` and friends on code paths their browser builds never take. webpack answered with `resolve.fallback: { fs: false, … }`; esbuild has no such option, so the same list is a resolve hook onto one empty CommonJS module (`RENDERER_EMPTY_MODULES` in the task). `electron` is on the list too: a renderer that reached the real module would be a security hole, not a missing polyfill.
|
|
99
|
-
|
|
100
|
-
### OMEGA_BUILD_JSON: a define for Node, one file for the browser
|
|
101
|
-
|
|
102
|
-
The wrapper is the ONE shape every OMEGA browser surface carries, `{ config, package, mode, license, builtAt }` ([#894](https://github.com/Omega-JS-Stack/omega/issues/894)), with `mode` the same three keys everywhere (`{ environment, build, publish }`; desktop's own `server` verdict stays inside `build.getMode()`). Two blobs come out of one composition, off one set of build facts:
|
|
103
|
-
|
|
104
|
-
- `composeBuildJson()` → main and preload, as an esbuild `define` (the bare identifier becomes the literal at compile time) plus a `banner` that assigns it to `globalThis`. Its `config` is the WHOLE resolved config, because the main process boots from it in a packaged app, and both bundles are Node rather than a public surface. `process.env.NODE_ENV` is defined the same way: webpack derived it from its `mode`, esbuild has no modes, so the build states it.
|
|
105
|
-
- `composeClientBuildJson()` → the renderer, written ONCE as `dist/build.js` through `@omega.js/devkit/build-json` ([#743](https://github.com/Omega-JS-Stack/omega/issues/743)). Its `config` is `clientConfig(resolved)` from `@omega.js/config`, the browser-safe subset every OMEGA browser surface carries: a renderer is readable from DevTools, so the GCP account facts, the signing certificates and the account admins stay out of it. The page template loads the file with `<script src="../../build.js">` as the view's FIRST script, ahead of the view bundle (`dist/views/<view>/` → `dist/`, resolved inside a packaged asar exactly as the bundle tag beside it is), and the renderer bundle carries no define and no banner of its own.
|
|
106
|
-
|
|
107
|
-
The build facts ride on both: `runtime: 'electron'` ([#896](https://github.com/Omega-JS-Stack/omega/issues/896), the fact @omega.js/client cannot sniff from inside a renderer), `environment`, `version`, `buildTime`, `target`, and the resolved `dev` map on non-production builds.
|
|
108
|
-
|
|
109
|
-
Pinned by `src/test/suites/build/build-json-bake.test.js`.
|
|
110
|
-
|
|
111
|
-
## electron-builder
|
|
112
|
-
|
|
113
|
-
@omega.js/desktop **generates** `dist/electron-builder.yml` from `config/omega.json5` + @omega.js/desktop defaults — the consumer never ships an `electron-builder.yml`. `gulp/build-config` does the materialization, applying:
|
|
114
|
-
|
|
115
|
-
- App metadata: `appId`, `productName`, `copyright` (with `{YEAR}` token expansion to the current year)
|
|
116
|
-
- App-level cross-platform fields: `category` mapping, `languages`, `darkModeSupport`
|
|
117
|
-
- Per-target (per-platform) installer config from `targets.{mac,win,linux}`:
|
|
118
|
-
- **mac**: arch (default `universal`), MAS stubs (not implemented)
|
|
119
|
-
- **win**: arch (default `x64`+`ia32`), NSIS oneClick + shortcuts
|
|
120
|
-
- **linux**: arch, optional snap publishing
|
|
121
|
-
- Target lists derived from the `platforms` declaration and versionless `artifactName` templates from `@omega.js/config`'s `platforms.js`: the ONE format table the website's direct-download URLs read too, so `/releases/latest/download/<asset>` never changes. The asset table + the whole release contract: [releasing.md](releasing.md#versionless-assets-and-direct-download-links)
|
|
122
|
-
- Mode-dependent injections like `mac.extendInfo.LSUIElement: true` when `startup.mode === 'hidden'` (zero-bounce production launches — see [startup.md](startup.md))
|
|
123
|
-
- `electronVersion` pinned from the INSTALLED electron (resolved via the framework's module context — electron-builder refuses semver ranges and can't see a workspace-hoisted electron from the target dir)
|
|
124
|
-
- Generated entitlements + resolved icons + materialized publish + afterSign hook. The publish block is CONFIG-ONLY and fully derived ([#883](https://github.com/Omega-JS-Stack/omega/issues/883)): `<brand.id>-releases` under `repo.org`, through @omega.js/config's `releasesRepo`. No git-remote discovery and no typed repo name (a brand-monorepo target's remote is the repo it is nested in; electron-builder's update-info step crashes on a null publish config, so this isn't cosmetic)
|
|
125
|
-
- Optional passthrough: `fileAssociations`, `protocols`
|
|
126
|
-
- The `files` list: everything under the target root except source maps, `.env` files, `logs/`, and the scratch and state dirs `.omega/`, `.claude/`, `.temp/`, `.cache/`, `.gh-runners/` and `test/` ([#866](https://github.com/Omega-JS-Stack/omega/issues/866): the boot runner stages `.omega/test-app` with symlinks into the target, and the packager followed them). `src/` ships, because the runtime reads `src/integrations/*` from the app root; `config/` (the build resources dir) and `release/` are excluded by electron-builder itself
|
|
127
|
-
|
|
128
|
-
The full per-target reference (every config knob, default value, and what it produces in YAML) lives in **[installer-options.md](installer-options.md)**.
|
|
129
|
-
|
|
130
|
-
`gulp/package` and `gulp/package-quick` both point electron-builder at the generated `dist/electron-builder.yml`. Consumer overrides via `config.electronBuilder.*` are merged on top of the generated config — see [installer-options.md § Raw `electronBuilder` overrides](installer-options.md#raw-electronbuilder-overrides-escape-hatch) for the escape hatch.
|
|
131
|
-
|
|
132
|
-
## Build modes
|
|
133
|
-
|
|
134
|
-
Environment variables (set in-process by the `omega build` / `omega package` / `omega publish` verbs — the consumer's npm scripts are thin `npx omega` aliases):
|
|
135
|
-
|
|
136
|
-
| Var | Effect |
|
|
137
|
-
|---|---|
|
|
138
|
-
| `OMEGA_BUILD_MODE=true` | Production bundles (minified, name-mangled, no sourcemaps, `@dev-only` blocks stripped) |
|
|
139
|
-
| `OMEGA_BUILD_OUTPUT=<path>` | The boot-test seam: redirect the gulp BUILD output away from `<project>/dist` (absolute, or relative to the project root). Resolved by [src/utils/dist-root.js](../src/utils/dist-root.js), which every build task's output path goes through — but `omega clean` and the generated `electron-builder.yml` stay project-relative, so this is NOT a general relocation switch; packaging under it is unsupported. Used by the boot-test runner so a test build never collides with the `npm start` watcher's `dist/` ([test-boot-layer.md](test-boot-layer.md#isolated-build-output)) |
|
|
140
|
-
| `OMEGA_IS_PUBLISH=true` | electron-builder runs with `--publish always` |
|
|
141
|
-
| `OMEGA_IS_SERVER=true` | Running in CI |
|
|
142
|
-
|
|
143
|
-
## Windows code signing
|
|
144
|
-
|
|
145
|
-
Strategy-pluggable via `platforms.windows.signing.strategy` in `config/omega.json5`:
|
|
146
|
-
|
|
147
|
-
| Strategy | Where signing runs | When to use |
|
|
148
|
-
|---|---|---|
|
|
149
|
-
| `self-hosted` | Self-hosted GH Actions runner with USB EV token plugged in | Default for @omega.js/desktop v1 — physical EV token desktop |
|
|
150
|
-
| `cloud` | `windows-latest` runner shells out to a cloud signing CLI (Azure Trusted Signing / SSL.com / DigiCert KeyLocker) | Future migration target |
|
|
151
|
-
| `local` | Developer's Windows machine after CI uploads unsigned artifact | Fallback when no runner is available |
|
|
152
|
-
|
|
153
|
-
The `gulp/build-config` task and `electron-builder.yml`'s `win.sign` hook both honor `platforms.windows.signing.strategy` so the same code path drives all three. Provider modules live in `src/lib/sign-providers/{ev,azure,sslcom,digicert}.js` (Pass 3).
|
|
154
|
-
|
|
155
|
-
## GitHub Actions
|
|
156
|
-
|
|
157
|
-
`.github/workflows/build.yml` (in `src/defaults/`) runs a 3-OS matrix: macOS / Linux / Windows. Windows job uploads unsigned; a separate `windows-sign` job runs on the strategy-appropriate runner and attaches signed artifacts to the release.
|
|
158
|
-
|
|
159
|
-
Env vars set globally:
|
|
160
|
-
|
|
161
|
-
```yaml
|
|
162
|
-
NODE_VERSION: '22'
|
|
163
|
-
OMEGA_BUILD_MODE: 'true'
|
|
164
|
-
OMEGA_IS_PUBLISH: 'true'
|
|
165
|
-
OMEGA_IS_SERVER: 'true'
|
|
166
|
-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Concurrency group: `${{ github.ref }}` with `cancel-in-progress`.
|
package/docs/cdp-debugging.md
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
# CDP Debugging (Claude ↔ Electron)
|
|
2
|
-
|
|
3
|
-
@omega.js/desktop's `serve` task forwards all `--` CLI flags to the Electron child process. This enables Chrome DevTools Protocol (CDP) debugging, which lets Claude (or any CDP client) interact with the running Electron app — take screenshots, click elements, type text, evaluate JS, read console logs, inspect network requests, etc.
|
|
4
|
-
|
|
5
|
-
Two ways to use it: **the built-in `npx omega cdp` toolkit** (below — zero setup, multi-target, knows @omega.js/desktop's conventions) and the `chrome-devtools-electron` MCP (further down — richer single-page interaction: click/fill/network/traces).
|
|
6
|
-
|
|
7
|
-
## Launching with CDP
|
|
8
|
-
|
|
9
|
-
Two equivalent ways:
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
# Env var (recommended)
|
|
13
|
-
OMEGA_CDP_PORT=9222 npm start
|
|
14
|
-
|
|
15
|
-
# CLI flag
|
|
16
|
-
npm start -- --remote-debugging-port=9222
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Both add `--remote-debugging-port=9222` to the Electron spawn args. The env var takes precedence if both are set; a CLI flag already present won't be duplicated.
|
|
20
|
-
|
|
21
|
-
Verify CDP is live:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
curl -s http://localhost:9222/json
|
|
25
|
-
# Returns JSON array of page targets (one per BrowserWindow)
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## The `mgr cdp` toolkit
|
|
29
|
-
|
|
30
|
-
Zero-dependency subcommands for driving the running dev app — see, act, and run the no-watch iterate loop. All read `OMEGA_CDP_PORT` (default 9222), or take `--port <n>` (which wins).
|
|
31
|
-
|
|
32
|
-
**Pinning a port in an npm script? Use `--port`, never `cross-env`.** `npx cross-env OMEGA_CDP_PORT=… npx omega cdp eval … "window.desktop.ipc.invoke('my:channel')"` STRIPS the inner quotes from the expression before it reaches V8 (`invoke(my:channel)` → `SyntaxError: missing ) after argument list`). `npx omega cdp eval … "…" --port <n>` keeps the expression intact.
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
npx omega cdp status # running? targets, window rect, theme
|
|
36
|
-
npx omega cdp eval <match> '<expr>' # evaluate JS in any webContents
|
|
37
|
-
npx omega cdp shot <match> <out.png> # ONE renderer's own pixels
|
|
38
|
-
npx omega cdp capture <out.png> # the COMPOSITED window (macOS)
|
|
39
|
-
npx omega cdp theme <dark|light|system> # flip the live theme (omega.theme)
|
|
40
|
-
npx omega cdp relaunch # quit → npm start → wait for boot
|
|
41
|
-
npx omega cdp quit # quit + wait for the process tree to drain
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### The multi-target model
|
|
45
|
-
|
|
46
|
-
An @omega.js/desktop app is one window but potentially MANY webContents (every BrowserWindow + every `WebContentsView` is its own CDP page target). Every subcommand takes a **URL-substring matcher** instead of a "current page": the main window's document is always `/views/main/` (@omega.js/desktop's templating convention); other views match by their own URLs. `status` lists what's live.
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
npx omega cdp eval "/views/main/" 'document.title'
|
|
50
|
-
npx omega cdp eval "example.com" 'getComputedStyle(document.body).backgroundColor'
|
|
51
|
-
npx omega cdp eval "/views/main/" "window.desktop.ipc.invoke('my-app:some-channel')" # the real IPC surface
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Promises are awaited, results print as JSON, and expressions run with a user gesture (focus()/clipboard-ish APIs behave like real input).
|
|
55
|
-
|
|
56
|
-
### shot vs capture — the compositing discriminator
|
|
57
|
-
|
|
58
|
-
`shot` asks ONE renderer for its own surface; `capture` photographs the window the OS composited (the BrowserWindow document + every WebContentsView stacked). They answer different questions:
|
|
59
|
-
|
|
60
|
-
- Styling wrong in one view? → `shot` that target.
|
|
61
|
-
- Layering/transparency/z-order wrong? → `capture`; if `shot` looks right but `capture` doesn't, the bug is in compositing, not your CSS.
|
|
62
|
-
|
|
63
|
-
Caveats:
|
|
64
|
-
- `shot` needs a VISIBLE surface — a hidden view (`setVisible(false)`, a background view) produces no compositor frames and the capture times out. Show/select it first.
|
|
65
|
-
- `capture` raises the app and region-captures its rect — anything overlaying that region still wins. For an occlusion-proof image: `--find-window-id` (slow Swift path; JXA's CoreGraphics bridge segfaults) → `capture <out.png> --window-id <id>` (the ID is stable per window lifetime — cache it).
|
|
66
|
-
- **Color profiles:** macOS embeds the MONITOR's ICC profile in screenshot PNGs; many viewers (including image previews in tooling) misrender it dramatically — an opaque dark panel can read near-white. `capture` converts every file to sRGB via `sips`, so it's portable. Hand-rolled screencaptures should do the same: `sips -m "/System/Library/ColorSync/Profiles/sRGB Profile.icc" shot.png --out shot.png`.
|
|
67
|
-
- `capture`, `relaunch`, and `quit` are macOS-only (screencapture / sips / osascript). `status`/`eval`/`shot`/`theme` are cross-platform.
|
|
68
|
-
|
|
69
|
-
### relaunch / quit — the iterate loop
|
|
70
|
-
|
|
71
|
-
@omega.js/desktop dev has **no watch** (`npm start` builds once, then runs) — every `src/` edit needs quit → rebuild → boot. `relaunch` is that loop in one command: it quits the app (real quit — `before-quit` handlers run), waits for the **full process tree to drain** (port-down alone is NOT that signal — the npm-start chain takes a few more seconds, and a test run started inside that window gets contaminated with flaky boot suites), spawns a detached `npm start` with `OMEGA_CDP_PORT`, and waits for the boot signal. `quit` is the first half alone — safe to run `npx omega test` the moment it returns. **Never run tests while the app is going down** (a test run started before the process tree drains gets contaminated) — boot tests build into their own `.omega/test-app/` output since #110, so `dist/` itself no longer collides.
|
|
72
|
-
|
|
73
|
-
The boot signal defaults to the main window's document target. Apps whose boot completes later than first paint override it in `config/omega.json5`:
|
|
74
|
-
|
|
75
|
-
```json5
|
|
76
|
-
cdp: {
|
|
77
|
-
readySignal: 'my-app://overlay', // URL substring of the target that appears LAST in boot
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
The packaged-app process name (for quit/raise/window-id matching) comes from config too: `app.productName` (derived from `brand.name`); dev builds run under "Electron".
|
|
82
|
-
|
|
83
|
-
## Driving a regular Chrome (not the app)
|
|
84
|
-
|
|
85
|
-
Sometimes the thing to drive is a regular **Chrome** — the marketing site, a web flow, an OAuth page — not the Electron app.
|
|
86
|
-
|
|
87
|
-
> Mirrored across the five sister frameworks (UJM / @omega.js/backend / BXM / @omega.js/desktop / @omega.js/client) — same core section, framework-flavored. Edit all five together.
|
|
88
|
-
|
|
89
|
-
Browser work runs through the **`chrome-devtools` MCP** (via mcp-router). There is NO launch procedure anymore — no ports, no profile dirs, no curl checks:
|
|
90
|
-
|
|
91
|
-
- **Just call the tools** — `new_page`, `navigate_page`, `take_screenshot`, `click`, `fill`, `evaluate_script`, `list_console_messages`, `list_network_requests`. The browser auto-launches on the first call.
|
|
92
|
-
- **Each Claude session gets its OWN private Chrome** (`--isolated`): temp profile, CDP over an internal pipe. Parallel sessions cannot see or touch each other's pages — open and close pages freely, the whole browser is yours.
|
|
93
|
-
- **It dies with the session.** No orphans, no cleanup, nothing to kill.
|
|
94
|
-
- **Ephemeral profile** — cookies/logins do NOT persist between sessions. If a flow needs auth, log in during the task.
|
|
95
|
-
- **Self-signed HTTPS is pre-accepted** (`--acceptInsecureCerts` in the upstream) — dev servers load without certificate interstitials.
|
|
96
|
-
- **NEVER quit/kill Chrome by app name** (`killall "Google Chrome"`, osascript) — that's the user's personal browser, not yours.
|
|
97
|
-
|
|
98
|
-
Humans: the agent's Chrome window is visible — you can watch it drive. Full reference: `~/.claude/mcp-server/servers/chrome-devtools/CLAUDE.md`.
|
|
99
|
-
|
|
100
|
-
@omega.js/desktop specifics:
|
|
101
|
-
|
|
102
|
-
- **The Electron app stays attach-by-port** — that's the whole rest of this doc (`mgr cdp` per invocation, or the `chrome-devtools-electron` MCP below). Port convention: **9222** = the Electron app. The isolated `chrome-devtools` browser has nothing to do with the app.
|
|
103
|
-
- **Navigating to a brand's UJM dev site (the local marketing site)?** **`https://localhost:4000` — NEVER the LAN IP** (`https://192.168.x.x:...`). Port 4000 by default, increments to 4001+ when multiple sites run; exact port in `.temp/_config_browsersync.yml` at the root of the WEBSITE project (the UJM consumer — e.g. `<brand>-website/.temp/_config_browsersync.yml`, NOT this app repo).
|
|
104
|
-
|
|
105
|
-
## What CDP exposes
|
|
106
|
-
|
|
107
|
-
- **Renderer processes only** — one "page" target per BrowserWindow. Main process is NOT exposed (use `--inspect=9229` for that, which is a separate V8 inspector protocol).
|
|
108
|
-
- Each BrowserWindow appears as a separate page target. MCP tools with `list_pages`/`select_page` can switch between them.
|
|
109
|
-
- Chromium silently ignores flags it doesn't recognize, so gulp's own flags (`--cwd`, `--gulpfile`) pass through harmlessly.
|
|
110
|
-
|
|
111
|
-
## MCP setup (Claude ↔ Electron)
|
|
112
|
-
|
|
113
|
-
A `chrome-devtools-electron` MCP upstream is configured at `~/.claude/mcp-server/servers/chrome-devtools-electron/config.json`. It reads `OMEGA_CDP_PORT` from the environment at spawn time (defaults to 9222), so the same upstream works for any Electron app — just set the env var **BEFORE launching `claude`** (it's expanded once, when the session's router spawns the upstream; mid-session changes do nothing).
|
|
114
|
-
|
|
115
|
-
```json
|
|
116
|
-
{
|
|
117
|
-
"enabled": true,
|
|
118
|
-
"command": "sh",
|
|
119
|
-
"args": ["-c", "exec /Users/ian/.nvm/default-bin/npx -y chrome-devtools-mcp@latest --browserUrl=http://127.0.0.1:${OMEGA_CDP_PORT:-9222} --usage-statistics=false"]
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
This runs alongside the regular `chrome-devtools` upstream (which launches its own per-session isolated browser — no env vars, no ports). Tools are namespaced:
|
|
124
|
-
- `chrome-devtools__take_screenshot` → the session's own Chrome browser
|
|
125
|
-
- `chrome-devtools-electron__take_screenshot` → the running Electron app
|
|
126
|
-
|
|
127
|
-
Multiple Claude sessions can debug different apps simultaneously — each terminal sets its own `OMEGA_CDP_PORT`. See `~/.claude/mcp-server/README.md`.
|
|
128
|
-
|
|
129
|
-
### Available MCP tools (29)
|
|
130
|
-
|
|
131
|
-
Screenshots, click, fill, type, hover, drag, evaluate JS, list/read console messages, list/inspect network requests, navigate, resize, keyboard input, accessibility snapshots, Lighthouse audits, performance traces, heap snapshots, dialog handling.
|
|
132
|
-
|
|
133
|
-
### Session setup
|
|
134
|
-
|
|
135
|
-
If the upstream was added/enabled after a Claude session started, its tools won't appear until next session. To enable mid-session:
|
|
136
|
-
|
|
137
|
-
```
|
|
138
|
-
# From shell
|
|
139
|
-
mcp enable chrome-devtools-electron
|
|
140
|
-
|
|
141
|
-
# From inside Claude (if upstream is enabled on disk but not in session)
|
|
142
|
-
router__enable_upstream { name: "chrome-devtools-electron" }
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## Port conventions
|
|
146
|
-
|
|
147
|
-
| Port | Protocol | Usage |
|
|
148
|
-
|------|----------|-------|
|
|
149
|
-
| 9222 | CDP (HTTP + WebSocket) | Electron renderer debugging (standard CDP port) |
|
|
150
|
-
| 9229 | V8 Inspector | Node.js / Electron main process debugging (`--inspect`) |
|
|
151
|
-
|
|
152
|
-
Use 9222 for `--remote-debugging-port` (industry standard). Avoid 9229 — that's the Node.js inspector port and a different protocol.
|
|
153
|
-
|
|
154
|
-
## Security
|
|
155
|
-
|
|
156
|
-
CDP gives full control of the renderer — any local process can connect and read/modify anything. **Never ship with `--remote-debugging-port` baked in.** It's dev-only, gated behind `OMEGA_CDP_PORT` which is never set in production.
|
|
157
|
-
|
|
158
|
-
## How it works
|
|
159
|
-
|
|
160
|
-
`src/gulp/tasks/serve.js` collects extra args in two ways:
|
|
161
|
-
|
|
162
|
-
1. **CLI flags**: `process.argv.slice(2).filter(arg => arg.startsWith('--'))` — forwards all `--` flags from the gulp process to Electron
|
|
163
|
-
2. **`OMEGA_CDP_PORT` env var**: if set and no `--remote-debugging-port` is already in the args, appends `--remote-debugging-port=${OMEGA_CDP_PORT}`
|
|
164
|
-
|
|
165
|
-
The args are passed to `spawn(electronBin, ['.', ...extraArgs])`. The main process boot log shows the received argv:
|
|
166
|
-
|
|
167
|
-
```
|
|
168
|
-
[info] (main) Initializing @omega.js/desktop (main)... argv=[".","--remote-debugging-port=9222"]
|
|
169
|
-
```
|