mirinjs 0.1.0-alpha.14 → 0.1.0-alpha.16
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/package.json +2 -2
- package/src/{app.ts → app/facade.ts} +31 -117
- package/src/app/index.ts +2 -0
- package/src/app/lib/emitter.ts +40 -0
- package/src/app/lib/material.ts +9 -0
- package/src/app/types.ts +65 -0
- package/src/client.ts +12 -11
- package/src/config/index.ts +1 -0
- package/src/{config.ts → config/manifest.ts} +17 -3
- package/src/dialog.ts +1 -1
- package/src/host.ts +4 -8
- package/src/index.ts +34 -32
- package/src/logger.ts +7 -3
- package/src/menu.ts +17 -4
- package/src/native.ts +15 -3
- package/src/notification.ts +19 -0
- package/src/rpc-server.ts +13 -1
- package/src/rpc.ts +4 -5
- package/src/runtime.ts +21 -10
- package/src/shortcut.ts +1 -1
- package/src/sidecar.ts +13 -6
- package/src/tray.ts +1 -1
- package/src/updater/index.ts +2 -0
- package/src/updater/lib/apply.ts +116 -0
- package/src/updater/lib/archive.ts +41 -0
- package/src/updater/lib/integrity.ts +70 -0
- package/src/updater/lib/manifest.ts +119 -0
- package/src/updater/lib/platform.ts +19 -0
- package/src/updater/lib/urls.ts +44 -0
- package/src/updater/types.ts +66 -0
- package/src/updater/updater.ts +308 -0
- package/src/updater.ts +0 -468
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mirinjs",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.16",
|
|
4
4
|
"description": "Build desktop apps with Bun, TypeScript, and Chromium (CEF). The Bun-native Electron alternative.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
],
|
|
24
24
|
"exports": {
|
|
25
25
|
".": "./src/index.ts",
|
|
26
|
-
"./config": "./src/config.ts",
|
|
26
|
+
"./config": "./src/config/index.ts",
|
|
27
27
|
"./rpc": "./src/rpc.ts",
|
|
28
28
|
"./client": "./src/client.ts",
|
|
29
29
|
"./codec": "./src/codec.ts",
|
|
@@ -3,95 +3,35 @@
|
|
|
3
3
|
* (docs/api-design.md). Talks to the native core through the runtime.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import type { Router
|
|
8
|
-
import type
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
focus: void;
|
|
26
|
-
blur: void;
|
|
27
|
-
moved: void;
|
|
28
|
-
resized: void;
|
|
29
|
-
closed: void;
|
|
30
|
-
/** Fired when a native background material is applied (see setMaterial). */
|
|
31
|
-
material: WindowMaterialInfo;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export type AppEvents = {
|
|
35
|
-
ready: void;
|
|
36
|
-
"window-all-closed": void;
|
|
37
|
-
/** A deep-link URL (a registered `urlSchemes` scheme) opened the app, or
|
|
38
|
-
* arrived while it was running. Includes the launch URL. */
|
|
39
|
-
"open-url": string;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/** A window's frame in screen points (bottom-left origin, like AppKit). */
|
|
43
|
-
export interface WindowFrame {
|
|
44
|
-
x: number;
|
|
45
|
-
y: number;
|
|
46
|
-
width: number;
|
|
47
|
-
height: number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
type Listener<P> = (payload: P) => void;
|
|
51
|
-
|
|
52
|
-
class Emitter<Events extends Record<string, unknown>> {
|
|
53
|
-
#listeners = new Map<keyof Events, Set<Listener<unknown>>>();
|
|
54
|
-
|
|
55
|
-
on<K extends keyof Events>(type: K, listener: Listener<Events[K]>): () => void {
|
|
56
|
-
let set = this.#listeners.get(type);
|
|
57
|
-
if (!set) this.#listeners.set(type, (set = new Set()));
|
|
58
|
-
set.add(listener as Listener<unknown>);
|
|
59
|
-
return () => set!.delete(listener as Listener<unknown>);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async *events<K extends keyof Events>(type: K): AsyncIterableIterator<Events[K]> {
|
|
63
|
-
const queue: Events[K][] = [];
|
|
64
|
-
let wake: (() => void) | undefined;
|
|
65
|
-
const off = this.on(type, (payload) => {
|
|
66
|
-
queue.push(payload);
|
|
67
|
-
wake?.();
|
|
68
|
-
});
|
|
69
|
-
try {
|
|
70
|
-
while (true) {
|
|
71
|
-
while (queue.length) yield queue.shift()!;
|
|
72
|
-
await new Promise<void>((resolve) => (wake = resolve));
|
|
73
|
-
}
|
|
74
|
-
} finally {
|
|
75
|
-
off();
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
protected emit<K extends keyof Events>(type: K, payload: Events[K]): void {
|
|
80
|
-
this.#listeners.get(type)?.forEach((fn) => fn(payload));
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export interface WindowOpenOptions extends WindowConfig {
|
|
85
|
-
name?: string;
|
|
86
|
-
}
|
|
6
|
+
import type { WindowMaterial, WindowMaterialOptions } from "../config/index.ts";
|
|
7
|
+
import type { Router } from "../rpc.ts";
|
|
8
|
+
import { type NativeEvent, onNativeEvent, resolveUrl, runtime } from "../runtime.ts";
|
|
9
|
+
import type { SidecarOptions, SidecarProcess } from "../sidecar.ts";
|
|
10
|
+
import { sidecar as spawnSidecar } from "../sidecar.ts";
|
|
11
|
+
import type { Updater } from "../updater/index.ts";
|
|
12
|
+
import { updater } from "../updater/index.ts";
|
|
13
|
+
import { Emitter } from "./lib/emitter.ts";
|
|
14
|
+
import { normalizeMaterial } from "./lib/material.ts";
|
|
15
|
+
import type {
|
|
16
|
+
AppEvents,
|
|
17
|
+
BroadcastEmitters,
|
|
18
|
+
Dock,
|
|
19
|
+
ServeHandle,
|
|
20
|
+
WindowEvents,
|
|
21
|
+
WindowFrame,
|
|
22
|
+
WindowMaterialInfo,
|
|
23
|
+
WindowOpenOptions,
|
|
24
|
+
} from "./types.ts";
|
|
87
25
|
|
|
88
26
|
/** A live, typed handle to an open window. */
|
|
89
27
|
export class WindowHandle extends Emitter<WindowEvents> {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
) {
|
|
28
|
+
readonly id: number;
|
|
29
|
+
readonly name: string | undefined;
|
|
30
|
+
|
|
31
|
+
constructor(id: number, name: string | undefined) {
|
|
94
32
|
super();
|
|
33
|
+
this.id = id;
|
|
34
|
+
this.name = name;
|
|
95
35
|
}
|
|
96
36
|
|
|
97
37
|
async setTitle(title: string): Promise<void> {
|
|
@@ -182,7 +122,10 @@ export class WindowHandle extends Emitter<WindowEvents> {
|
|
|
182
122
|
}
|
|
183
123
|
}
|
|
184
124
|
|
|
185
|
-
type RpcEmitters = Record<
|
|
125
|
+
type RpcEmitters = Record<
|
|
126
|
+
string,
|
|
127
|
+
{ emit(payload: unknown): void; broadcast(payload: unknown): void }
|
|
128
|
+
>;
|
|
186
129
|
|
|
187
130
|
function rpcEmitters(send: (method: string, payload: unknown) => void): RpcEmitters {
|
|
188
131
|
return new Proxy({} as RpcEmitters, {
|
|
@@ -195,20 +138,6 @@ function rpcEmitters(send: (method: string, payload: unknown) => void): RpcEmitt
|
|
|
195
138
|
});
|
|
196
139
|
}
|
|
197
140
|
|
|
198
|
-
/** Typed push emitters derived from a router's `event` procedures. */
|
|
199
|
-
export type BroadcastEmitters<R extends Router<any>> =
|
|
200
|
-
R extends Router<infer T>
|
|
201
|
-
? {
|
|
202
|
-
[K in keyof T as T[K] extends EventProc<any> ? K : never]: T[K] extends EventProc<infer P>
|
|
203
|
-
? { broadcast(payload: P): void }
|
|
204
|
-
: never;
|
|
205
|
-
}
|
|
206
|
-
: never;
|
|
207
|
-
|
|
208
|
-
export interface ServeHandle<R extends Router<any>> {
|
|
209
|
-
readonly rpc: BroadcastEmitters<R>;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
141
|
class Windows {
|
|
213
142
|
#byName = new Map<string, WindowHandle>();
|
|
214
143
|
#byId = new Map<number, WindowHandle>();
|
|
@@ -267,26 +196,10 @@ function manifestWindow(name: string): WindowOpenOptions {
|
|
|
267
196
|
return found;
|
|
268
197
|
}
|
|
269
198
|
|
|
270
|
-
/** Normalize the `material` option (name or object, or null) to native form. */
|
|
271
|
-
function normalizeMaterial(
|
|
272
|
-
material: WindowMaterial | WindowMaterialOptions | null | undefined,
|
|
273
|
-
): WindowMaterialOptions | null {
|
|
274
|
-
if (!material) return null;
|
|
275
|
-
return typeof material === "string" ? { type: material } : material;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/** macOS Dock-icon controls (no-ops off macOS). */
|
|
279
|
-
export interface Dock {
|
|
280
|
-
/** Hide the Dock icon and menu-bar presence (agent/accessory app). */
|
|
281
|
-
hide(): void;
|
|
282
|
-
/** Restore the Dock icon and menu bar. */
|
|
283
|
-
show(): void;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
199
|
class MirinApp extends Emitter<AppEvents> {
|
|
287
200
|
readonly windows = new Windows();
|
|
288
201
|
|
|
289
|
-
/**
|
|
202
|
+
/** Cross-platform auto-updater. Inert unless the app was built with `release` set. */
|
|
290
203
|
readonly updater: Updater = updater;
|
|
291
204
|
|
|
292
205
|
/** This app's bundle id (mirin.config `id`). `undefined` when run detached. */
|
|
@@ -328,6 +241,7 @@ class MirinApp extends Emitter<AppEvents> {
|
|
|
328
241
|
}
|
|
329
242
|
}
|
|
330
243
|
|
|
244
|
+
/** Type-level `any` preserves the caller's concrete router shape. */
|
|
331
245
|
serve<R extends Router<any>>(router: R): ServeHandle<R> {
|
|
332
246
|
runtime().rpc.setRouter(router);
|
|
333
247
|
return { rpc: this.rpc as BroadcastEmitters<R> };
|
package/src/app/index.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
type Listener<P> = (payload: P) => void;
|
|
2
|
+
|
|
3
|
+
export class Emitter<Events extends Record<string, unknown>> {
|
|
4
|
+
#listeners = new Map<keyof Events, Set<Listener<unknown>>>();
|
|
5
|
+
|
|
6
|
+
on<K extends keyof Events>(type: K, listener: Listener<Events[K]>): () => void {
|
|
7
|
+
let set = this.#listeners.get(type);
|
|
8
|
+
if (!set) {
|
|
9
|
+
set = new Set();
|
|
10
|
+
this.#listeners.set(type, set);
|
|
11
|
+
}
|
|
12
|
+
set.add(listener as Listener<unknown>);
|
|
13
|
+
return () => {
|
|
14
|
+
set.delete(listener as Listener<unknown>);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async *events<K extends keyof Events>(type: K): AsyncIterableIterator<Events[K]> {
|
|
19
|
+
const queue: Events[K][] = [];
|
|
20
|
+
let wake: (() => void) | undefined;
|
|
21
|
+
const off = this.on(type, (payload) => {
|
|
22
|
+
queue.push(payload);
|
|
23
|
+
wake?.();
|
|
24
|
+
});
|
|
25
|
+
try {
|
|
26
|
+
while (true) {
|
|
27
|
+
while (queue.length) yield queue.shift()!;
|
|
28
|
+
await new Promise<void>((resolve) => {
|
|
29
|
+
wake = resolve;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
} finally {
|
|
33
|
+
off();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
protected emit<K extends keyof Events>(type: K, payload: Events[K]): void {
|
|
38
|
+
for (const fn of this.#listeners.get(type) ?? []) fn(payload);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { WindowMaterial, WindowMaterialOptions } from "../../config/index.ts";
|
|
2
|
+
|
|
3
|
+
/** Normalize the `material` option (name or object, or null) to native form. */
|
|
4
|
+
export function normalizeMaterial(
|
|
5
|
+
material: WindowMaterial | WindowMaterialOptions | null | undefined,
|
|
6
|
+
): WindowMaterialOptions | null {
|
|
7
|
+
if (!material) return null;
|
|
8
|
+
return typeof material === "string" ? { type: material } : material;
|
|
9
|
+
}
|
package/src/app/types.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { WindowConfig } from "../config/index.ts";
|
|
2
|
+
import type { EventProc, Router } from "../rpc.ts";
|
|
3
|
+
|
|
4
|
+
/** Which native backend a window's `material` resolved to. */
|
|
5
|
+
export type WindowMaterialInfo = {
|
|
6
|
+
/** The material that was requested. */
|
|
7
|
+
requested: string;
|
|
8
|
+
/** What actually rendered: real Liquid Glass, a vibrancy material, or none. */
|
|
9
|
+
backend: "liquidGlass" | "vibrancy" | "none";
|
|
10
|
+
/** Whether Apple's Liquid Glass (NSGlassEffectView, macOS 26+) is available. */
|
|
11
|
+
liquidGlassAvailable: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type WindowEvents = {
|
|
15
|
+
focus: void;
|
|
16
|
+
blur: void;
|
|
17
|
+
moved: void;
|
|
18
|
+
resized: void;
|
|
19
|
+
closed: void;
|
|
20
|
+
/** Fired when a native background material is applied (see setMaterial). */
|
|
21
|
+
material: WindowMaterialInfo;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type AppEvents = {
|
|
25
|
+
ready: void;
|
|
26
|
+
"window-all-closed": void;
|
|
27
|
+
/** A deep-link URL (a registered `urlSchemes` scheme) opened the app, or
|
|
28
|
+
* arrived while it was running. Includes the launch URL. */
|
|
29
|
+
"open-url": string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** A window's frame in screen points (bottom-left origin, like AppKit). */
|
|
33
|
+
export interface WindowFrame {
|
|
34
|
+
x: number;
|
|
35
|
+
y: number;
|
|
36
|
+
width: number;
|
|
37
|
+
height: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface WindowOpenOptions extends WindowConfig {
|
|
41
|
+
name?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Typed push emitters derived from a router's `event` procedures.
|
|
45
|
+
* Type-level `any` keeps the caller's router payload inference intact. */
|
|
46
|
+
export type BroadcastEmitters<R extends Router<any>> = R extends Router<infer T>
|
|
47
|
+
? {
|
|
48
|
+
[K in keyof T as T[K] extends EventProc<any> ? K : never]: T[K] extends EventProc<infer P>
|
|
49
|
+
? { broadcast(payload: P): void }
|
|
50
|
+
: never;
|
|
51
|
+
}
|
|
52
|
+
: never;
|
|
53
|
+
|
|
54
|
+
/** Type-level `any` preserves the caller's concrete router shape. */
|
|
55
|
+
export interface ServeHandle<R extends Router<any>> {
|
|
56
|
+
readonly rpc: BroadcastEmitters<R>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** macOS Dock-icon controls (no-ops off macOS). */
|
|
60
|
+
export interface Dock {
|
|
61
|
+
/** Hide the Dock icon and menu-bar presence (agent/accessory app). */
|
|
62
|
+
hide(): void;
|
|
63
|
+
/** Restore the Dock icon and menu bar. */
|
|
64
|
+
show(): void;
|
|
65
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -7,18 +7,18 @@
|
|
|
7
7
|
|
|
8
8
|
import type { EventProc, MutationProc, QueryProc, Router } from "./rpc.ts";
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
// Type-level `any` preserves the app router's concrete procedure inference for UI callers.
|
|
11
|
+
export type ClientFor<R extends Router<any>> = R extends Router<infer T>
|
|
12
|
+
? {
|
|
13
|
+
[K in keyof T]: T[K] extends QueryProc<infer I, infer O>
|
|
14
|
+
? (input: I) => Promise<Awaited<O>>
|
|
15
|
+
: T[K] extends MutationProc<infer I, infer O>
|
|
14
16
|
? (input: I) => Promise<Awaited<O>>
|
|
15
|
-
: T[K] extends
|
|
16
|
-
? (
|
|
17
|
-
:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
: never;
|
|
17
|
+
: T[K] extends EventProc<infer P>
|
|
18
|
+
? { on(listener: (payload: P) => void): () => void }
|
|
19
|
+
: never;
|
|
20
|
+
}
|
|
21
|
+
: never;
|
|
22
22
|
|
|
23
23
|
interface MirinTransport {
|
|
24
24
|
call(method: string, input: unknown): Promise<unknown>;
|
|
@@ -49,6 +49,7 @@ function transport(): MirinTransport {
|
|
|
49
49
|
* Create the typed client for the app's router. Pass the Router *type* only —
|
|
50
50
|
* never import the router value (and its handlers) into UI code.
|
|
51
51
|
*/
|
|
52
|
+
// Type-level `any` keeps `client<AppRouter>()` inference intact.
|
|
52
53
|
export function client<R extends Router<any>>(): ClientFor<R> {
|
|
53
54
|
const eventCache = new Map<string, { on(l: (p: unknown) => void): () => void }>();
|
|
54
55
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./manifest.ts";
|
|
@@ -97,8 +97,10 @@ export interface MirinConfig {
|
|
|
97
97
|
* `.ico` for the exe and window.
|
|
98
98
|
*/
|
|
99
99
|
icon?: string;
|
|
100
|
+
/** CEF runtime packaging options. Omit to bundle every locale. */
|
|
101
|
+
cef?: CefConfig;
|
|
100
102
|
/**
|
|
101
|
-
*
|
|
103
|
+
* Cross-platform auto-update configuration. Omit to disable updates entirely.
|
|
102
104
|
* `baseUrl` is a flat directory of release artifacts (a GitHub Releases
|
|
103
105
|
* `…/releases/latest/download` URL, an S3/R2 bucket, or any static host).
|
|
104
106
|
* `channel` (default "stable") is baked into artifact filenames + the app's
|
|
@@ -140,7 +142,8 @@ export interface MirinConfig {
|
|
|
140
142
|
/**
|
|
141
143
|
* External binaries bundled into the `.app` and spawned at runtime with
|
|
142
144
|
* `app.sidecar(name)`. Maps a logical name to a path (relative to the project
|
|
143
|
-
* root) or a {@link SidecarSpec}.
|
|
145
|
+
* root) or a {@link SidecarSpec}. Names must be filename-safe (`A-Z`, `a-z`,
|
|
146
|
+
* `0-9`, `.`, `_`, `-`) and paths must stay under the project root. Each binary is copied into
|
|
144
147
|
* `Contents/Resources/sidecars/<name>`, codesigned (hardened runtime), and —
|
|
145
148
|
* when notary credentials are set — notarized with the rest of the app.
|
|
146
149
|
*
|
|
@@ -151,7 +154,8 @@ export interface MirinConfig {
|
|
|
151
154
|
sidecars?: Record<string, string | SidecarSpec>;
|
|
152
155
|
/**
|
|
153
156
|
* Extra Bun Worker entry files (relative to the project root) bundled next to
|
|
154
|
-
* the main worker.
|
|
157
|
+
* the main worker. Names follow the same filename-safe rule as sidecars, and
|
|
158
|
+
* paths must stay under the project root. Resolve one at runtime with `resolveWorker(name)` and pass
|
|
155
159
|
* it to `new Worker(...)` from `node:worker_threads`. These run off the main
|
|
156
160
|
* thread for CPU/IO offload and must NOT call window/native APIs — only the
|
|
157
161
|
* app worker owns AppKit/CEF (see docs/architecture.md).
|
|
@@ -307,6 +311,16 @@ export interface ReleaseConfig {
|
|
|
307
311
|
baseUrl: string;
|
|
308
312
|
/** Update channel; baked into artifact names + support dir. Default "stable". */
|
|
309
313
|
channel?: string;
|
|
314
|
+
/** Optional markdown release notes embedded in the update manifest. */
|
|
315
|
+
notes?: string;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export interface CefConfig {
|
|
319
|
+
/**
|
|
320
|
+
* Locale packs bundled with production apps, as BCP 47 tags such as
|
|
321
|
+
* `en-US`, `es-419`, or `pt-BR`. Omit to bundle every CEF locale.
|
|
322
|
+
*/
|
|
323
|
+
locales?: string[];
|
|
310
324
|
}
|
|
311
325
|
|
|
312
326
|
/** Identity function for typing/intellisense. `const` generic preserves window names as literal keys. */
|
package/src/dialog.ts
CHANGED
package/src/host.ts
CHANGED
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
* their `app://` URLs served from Contents/Resources.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { Worker } from "node:worker_threads";
|
|
18
|
-
import { dirname, join } from "node:path";
|
|
19
17
|
import { existsSync, readFileSync } from "node:fs";
|
|
18
|
+
import { dirname, join } from "node:path";
|
|
19
|
+
import { Worker } from "node:worker_threads";
|
|
20
20
|
import { Core } from "./native.ts";
|
|
21
21
|
|
|
22
22
|
// Bundle layout differs by platform: macOS `.app` puts the host in
|
|
@@ -45,15 +45,11 @@ if (!existsSync(corePath) || !existsSync(workerPath)) {
|
|
|
45
45
|
process.exit(1);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
const manifest = JSON.parse(
|
|
49
|
-
process.env.MIRIN_MANIFEST_JSON ?? readManifestFromBundle() ?? "{}",
|
|
50
|
-
);
|
|
48
|
+
const manifest = JSON.parse(process.env.MIRIN_MANIFEST_JSON ?? readManifestFromBundle() ?? "{}");
|
|
51
49
|
|
|
52
50
|
const coreConfig = JSON.parse(
|
|
53
51
|
process.env.MIRIN_CONFIG_JSON ??
|
|
54
|
-
JSON.stringify(
|
|
55
|
-
process.env.MIRIN_DEV_URL ? { dev: true } : { resources_path: resourcesDir },
|
|
56
|
-
),
|
|
52
|
+
JSON.stringify(process.env.MIRIN_DEV_URL ? { dev: true } : { resources_path: resourcesDir }),
|
|
57
53
|
);
|
|
58
54
|
// Opt out of single-instance (the core default) when the app allows multiple.
|
|
59
55
|
if (manifest.singleInstance === false) coreConfig.single_instance = false;
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* `globalShortcut`, and `logger` features (docs/api-design.md).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { app, wireAppEvents } from "./app.ts";
|
|
10
|
+
import { app, wireAppEvents } from "./app/index.ts";
|
|
11
11
|
import { boot } from "./runtime.ts";
|
|
12
12
|
|
|
13
13
|
// Side-effect imports: each feature subscribes its native-event handlers.
|
|
@@ -16,48 +16,50 @@ import "./tray.ts";
|
|
|
16
16
|
import "./dialog.ts";
|
|
17
17
|
import "./shortcut.ts";
|
|
18
18
|
|
|
19
|
-
export { app } from "./app.ts";
|
|
20
|
-
export { menu } from "./menu.ts";
|
|
21
|
-
export { Tray } from "./tray.ts";
|
|
22
|
-
export { dialog } from "./dialog.ts";
|
|
23
|
-
export { clipboard } from "./clipboard.ts";
|
|
24
|
-
export { globalShortcut } from "./shortcut.ts";
|
|
25
|
-
export { logger, setLogLevel, getLogLevel, Logger } from "./logger.ts";
|
|
26
|
-
export { updater, Updater } from "./updater.ts";
|
|
27
|
-
export { sidecar } from "./sidecar.ts";
|
|
28
|
-
export { NotAttachedError, resolveWorker } from "./runtime.ts";
|
|
29
|
-
|
|
30
19
|
export type {
|
|
20
|
+
AppEvents,
|
|
21
|
+
BroadcastEmitters,
|
|
22
|
+
Dock,
|
|
23
|
+
ServeHandle,
|
|
31
24
|
WindowEvents,
|
|
32
|
-
WindowMaterialInfo,
|
|
33
25
|
WindowFrame,
|
|
34
|
-
AppEvents,
|
|
35
26
|
WindowHandle,
|
|
27
|
+
WindowMaterialInfo,
|
|
36
28
|
WindowOpenOptions,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
} from "./app.ts";
|
|
41
|
-
export type { MenuItemTemplate, MenuRole } from "./menu.ts";
|
|
42
|
-
export type { LogLevel } from "./logger.ts";
|
|
43
|
-
export type { UpdaterStatus, UpdateInfo, UpdateProgress, UpdaterEvents } from "./updater.ts";
|
|
44
|
-
export type { SidecarOptions, SidecarProcess } from "./sidecar.ts";
|
|
45
|
-
export type { TrayOptions } from "./tray.ts";
|
|
46
|
-
export type { OpenDialogOptions, SaveDialogOptions, MessageDialogOptions } from "./dialog.ts";
|
|
29
|
+
} from "./app/index.ts";
|
|
30
|
+
export { app } from "./app/index.ts";
|
|
31
|
+
export { clipboard } from "./clipboard.ts";
|
|
47
32
|
export type {
|
|
48
|
-
|
|
49
|
-
WindowConfig,
|
|
50
|
-
WindowMaterial,
|
|
51
|
-
WindowMaterialOptions,
|
|
52
|
-
ReleaseConfig,
|
|
33
|
+
CefConfig,
|
|
53
34
|
DmgConfig,
|
|
54
|
-
NsisConfig,
|
|
55
35
|
InnoConfig,
|
|
56
36
|
LinuxConfig,
|
|
57
37
|
LinuxPackageFormat,
|
|
58
|
-
|
|
38
|
+
MirinConfig,
|
|
39
|
+
NsisConfig,
|
|
40
|
+
ReleaseConfig,
|
|
59
41
|
SidecarEntitlement,
|
|
60
|
-
|
|
42
|
+
SidecarSpec,
|
|
43
|
+
WindowConfig,
|
|
44
|
+
WindowMaterial,
|
|
45
|
+
WindowMaterialOptions,
|
|
46
|
+
} from "./config/index.ts";
|
|
47
|
+
export type { MessageDialogOptions, OpenDialogOptions, SaveDialogOptions } from "./dialog.ts";
|
|
48
|
+
export { dialog } from "./dialog.ts";
|
|
49
|
+
export type { LogLevel } from "./logger.ts";
|
|
50
|
+
export { getLogLevel, Logger, logger, setLogLevel } from "./logger.ts";
|
|
51
|
+
export type { MenuItemTemplate, MenuRole } from "./menu.ts";
|
|
52
|
+
export { menu } from "./menu.ts";
|
|
53
|
+
export type { NotificationOptions } from "./notification.ts";
|
|
54
|
+
export { notification } from "./notification.ts";
|
|
55
|
+
export { NotAttachedError, resolveWorker } from "./runtime.ts";
|
|
56
|
+
export { globalShortcut } from "./shortcut.ts";
|
|
57
|
+
export type { SidecarOptions, SidecarProcess } from "./sidecar.ts";
|
|
58
|
+
export { resolveSidecar, sidecar } from "./sidecar.ts";
|
|
59
|
+
export type { TrayOptions } from "./tray.ts";
|
|
60
|
+
export { Tray } from "./tray.ts";
|
|
61
|
+
export type { UpdateInfo, UpdateProgress, UpdaterEvents, UpdaterStatus } from "./updater/index.ts";
|
|
62
|
+
export { Updater, updater } from "./updater/index.ts";
|
|
61
63
|
|
|
62
64
|
wireAppEvents();
|
|
63
65
|
boot();
|
package/src/logger.ts
CHANGED
|
@@ -53,12 +53,16 @@ export function getLogLevel(): LogLevel {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export class Logger {
|
|
56
|
+
readonly #scope?: string;
|
|
57
|
+
|
|
56
58
|
/** @param scope optional dotted scope shown as `[mirin:scope]`. */
|
|
57
|
-
constructor(
|
|
59
|
+
constructor(scope?: string) {
|
|
60
|
+
this.#scope = scope;
|
|
61
|
+
}
|
|
58
62
|
|
|
59
63
|
/** Derive a scoped child logger (e.g. `logger.child("db")`). */
|
|
60
64
|
child(scope: string): Logger {
|
|
61
|
-
return new Logger(this
|
|
65
|
+
return new Logger(this.#scope ? `${this.#scope}:${scope}` : scope);
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
/** Set the global level (same as the exported `setLogLevel`). */
|
|
@@ -85,7 +89,7 @@ export class Logger {
|
|
|
85
89
|
|
|
86
90
|
#emit(level: Exclude<LogLevel, "silent">, args: unknown[]): void {
|
|
87
91
|
if (ORDER[level] < ORDER[currentLevel]) return;
|
|
88
|
-
const name = this
|
|
92
|
+
const name = this.#scope ? `[mirin:${this.#scope}]` : "[mirin]";
|
|
89
93
|
const prefix = useColor ? `${COLOR.dim}${name}${COLOR.reset}` : name;
|
|
90
94
|
const tag = useColor ? `${COLOR[level]}${level}${COLOR.reset}` : level;
|
|
91
95
|
const sink = level === "warn" || level === "error" ? console.error : console.log;
|
package/src/menu.ts
CHANGED
|
@@ -4,12 +4,25 @@
|
|
|
4
4
|
* carrying that id. `role` items map to AppKit's standard actions.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { onNativeEvent, runtime } from "./runtime.ts";
|
|
8
8
|
|
|
9
9
|
export type MenuRole =
|
|
10
|
-
| "quit"
|
|
11
|
-
| "
|
|
12
|
-
| "
|
|
10
|
+
| "quit"
|
|
11
|
+
| "close"
|
|
12
|
+
| "minimize"
|
|
13
|
+
| "zoom"
|
|
14
|
+
| "front"
|
|
15
|
+
| "togglefullscreen"
|
|
16
|
+
| "hide"
|
|
17
|
+
| "hideothers"
|
|
18
|
+
| "unhide"
|
|
19
|
+
| "undo"
|
|
20
|
+
| "redo"
|
|
21
|
+
| "cut"
|
|
22
|
+
| "copy"
|
|
23
|
+
| "paste"
|
|
24
|
+
| "selectall"
|
|
25
|
+
| "delete";
|
|
13
26
|
|
|
14
27
|
export interface MenuItemTemplate {
|
|
15
28
|
label?: string;
|
package/src/native.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* process, so the Rust statics behind these symbols are shared.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { dlopen, FFIType,
|
|
9
|
+
import { CString, dlopen, FFIType, type Pointer, ptr } from "bun:ffi";
|
|
10
10
|
|
|
11
11
|
function nullTerminated(s: string): Uint8Array {
|
|
12
12
|
return new TextEncoder().encode(s + "\0");
|
|
@@ -23,8 +23,14 @@ const symbols = {
|
|
|
23
23
|
mirin_window_set_title: { args: [FFIType.u32, FFIType.ptr], returns: FFIType.void },
|
|
24
24
|
mirin_window_control: { args: [FFIType.u32, FFIType.ptr], returns: FFIType.void },
|
|
25
25
|
mirin_window_set_material: { args: [FFIType.u32, FFIType.ptr], returns: FFIType.void },
|
|
26
|
-
mirin_window_set_position: {
|
|
27
|
-
|
|
26
|
+
mirin_window_set_position: {
|
|
27
|
+
args: [FFIType.u32, FFIType.f64, FFIType.f64],
|
|
28
|
+
returns: FFIType.void,
|
|
29
|
+
},
|
|
30
|
+
mirin_window_maybe_start_drag: {
|
|
31
|
+
args: [FFIType.u32, FFIType.i32, FFIType.i32, FFIType.i32, FFIType.i32],
|
|
32
|
+
returns: FFIType.void,
|
|
33
|
+
},
|
|
28
34
|
mirin_app_quit: { args: [], returns: FFIType.void },
|
|
29
35
|
mirin_app_set_dock_visible: { args: [FFIType.i32], returns: FFIType.void },
|
|
30
36
|
mirin_set_app_menu: { args: [FFIType.ptr], returns: FFIType.void },
|
|
@@ -36,6 +42,7 @@ const symbols = {
|
|
|
36
42
|
mirin_clipboard_read_text: { args: [], returns: FFIType.ptr },
|
|
37
43
|
mirin_clipboard_write_text: { args: [FFIType.ptr], returns: FFIType.void },
|
|
38
44
|
mirin_dialog_show: { args: [FFIType.ptr], returns: FFIType.void },
|
|
45
|
+
mirin_notification_show: { args: [FFIType.ptr], returns: FFIType.i32 },
|
|
39
46
|
} as const;
|
|
40
47
|
|
|
41
48
|
export type EventListener = (event: string) => void;
|
|
@@ -168,4 +175,9 @@ export class Core {
|
|
|
168
175
|
const buf = nullTerminated(specJson);
|
|
169
176
|
this.#lib.symbols.mirin_dialog_show(ptr(buf));
|
|
170
177
|
}
|
|
178
|
+
|
|
179
|
+
notificationShow(specJson: string): boolean {
|
|
180
|
+
const buf = nullTerminated(specJson);
|
|
181
|
+
return this.#lib.symbols.mirin_notification_show(ptr(buf)) === 1;
|
|
182
|
+
}
|
|
171
183
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { runtime } from "./runtime.ts";
|
|
2
|
+
|
|
3
|
+
export interface NotificationOptions {
|
|
4
|
+
title: string;
|
|
5
|
+
body?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const notification = {
|
|
9
|
+
/** Ask the operating system to display a desktop notification. */
|
|
10
|
+
show(options: NotificationOptions): boolean {
|
|
11
|
+
const title = options.title.trim();
|
|
12
|
+
if (!title || title.length > 128) return false;
|
|
13
|
+
if (options.body && options.body.length > 512) return false;
|
|
14
|
+
|
|
15
|
+
return runtime().core.notificationShow(
|
|
16
|
+
JSON.stringify({ ...options, title, appName: runtime().id }),
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
};
|