mirinjs 0.1.0-alpha.6 → 0.1.0-alpha.8
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 +1 -1
- package/src/app.ts +19 -0
- package/src/host.ts +3 -0
- package/src/runtime.ts +7 -0
- package/src/updater.ts +32 -19
package/package.json
CHANGED
package/src/app.ts
CHANGED
|
@@ -289,6 +289,25 @@ class MirinApp extends Emitter<AppEvents> {
|
|
|
289
289
|
/** Auto-updater (macOS). Inert unless the app was built with `release` set. */
|
|
290
290
|
readonly updater: Updater = updater;
|
|
291
291
|
|
|
292
|
+
/** This app's bundle id (mirin.config `id`). `undefined` when run detached. */
|
|
293
|
+
get id(): string | undefined {
|
|
294
|
+
try {
|
|
295
|
+
return runtime().id;
|
|
296
|
+
} catch {
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** True under `mirin dev`; false in a packaged build (and when detached). Use it
|
|
302
|
+
* to keep dev and installed data (caches, databases) separate. */
|
|
303
|
+
get isDev(): boolean {
|
|
304
|
+
try {
|
|
305
|
+
return runtime().isDev;
|
|
306
|
+
} catch {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
292
311
|
/** macOS Dock-icon controls. Hiding suits resident, hotkey-summoned apps. */
|
|
293
312
|
readonly dock: Dock = {
|
|
294
313
|
hide: () => this.#setDock(false),
|
package/src/host.ts
CHANGED
|
@@ -52,6 +52,8 @@ const coreConfig = JSON.parse(
|
|
|
52
52
|
);
|
|
53
53
|
// Opt out of single-instance (the core default) when the app allows multiple.
|
|
54
54
|
if (manifest.singleInstance === false) coreConfig.single_instance = false;
|
|
55
|
+
// The app's bundle id keys the per-app CEF cache dir (Windows has no OS bundle id).
|
|
56
|
+
if (typeof manifest.id === "string") coreConfig.identifier = manifest.id;
|
|
55
57
|
|
|
56
58
|
// Load the native core on the main thread FIRST. The Worker also dlopens the
|
|
57
59
|
// same dylib in its boot; doing the main-thread dlopen before spawning the
|
|
@@ -63,6 +65,7 @@ const worker = new Worker(workerPath, {
|
|
|
63
65
|
workerData: {
|
|
64
66
|
corePath,
|
|
65
67
|
manifest,
|
|
68
|
+
id: typeof manifest.id === "string" ? manifest.id : undefined,
|
|
66
69
|
devUrl: process.env.MIRIN_DEV_URL,
|
|
67
70
|
resourcesDir,
|
|
68
71
|
sidecarDir,
|
package/src/runtime.ts
CHANGED
|
@@ -18,6 +18,10 @@ export interface Runtime {
|
|
|
18
18
|
core: Core;
|
|
19
19
|
rpc: RpcServer;
|
|
20
20
|
manifestWindows: ManifestWindowConfig[];
|
|
21
|
+
/** App bundle id (mirin.config `id`); undefined when detached. */
|
|
22
|
+
id?: string;
|
|
23
|
+
/** True under `mirin dev`; false in a packaged build. */
|
|
24
|
+
isDev: boolean;
|
|
21
25
|
devUrl?: string;
|
|
22
26
|
/** Contents/Resources of the running .app (for the updater). Absent in dev. */
|
|
23
27
|
resourcesDir?: string;
|
|
@@ -87,6 +91,7 @@ export function boot(): void {
|
|
|
87
91
|
const data = (workerData ?? {}) as {
|
|
88
92
|
corePath?: string;
|
|
89
93
|
manifest?: { windows?: Record<string, WindowConfig> };
|
|
94
|
+
id?: string;
|
|
90
95
|
devUrl?: string;
|
|
91
96
|
resourcesDir?: string;
|
|
92
97
|
sidecarDir?: string;
|
|
@@ -126,6 +131,8 @@ export function boot(): void {
|
|
|
126
131
|
core,
|
|
127
132
|
rpc,
|
|
128
133
|
manifestWindows,
|
|
134
|
+
id: data.id,
|
|
135
|
+
isDev: !!data.devUrl,
|
|
129
136
|
devUrl: data.devUrl,
|
|
130
137
|
resourcesDir: data.resourcesDir,
|
|
131
138
|
corePath,
|
package/src/updater.ts
CHANGED
|
@@ -15,7 +15,15 @@
|
|
|
15
15
|
|
|
16
16
|
import { homedir, tmpdir } from "node:os";
|
|
17
17
|
import { join } from "node:path";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
existsSync,
|
|
20
|
+
mkdirSync,
|
|
21
|
+
readFileSync,
|
|
22
|
+
readdirSync,
|
|
23
|
+
renameSync,
|
|
24
|
+
rmSync,
|
|
25
|
+
writeFileSync,
|
|
26
|
+
} from "node:fs";
|
|
19
27
|
import { $ } from "bun";
|
|
20
28
|
import { runtime } from "./runtime.ts";
|
|
21
29
|
import { loadCodec } from "./codec.ts";
|
|
@@ -214,31 +222,36 @@ export class Updater {
|
|
|
214
222
|
if (IS_WINDOWS) {
|
|
215
223
|
const runningApp = join(this.#resourcesDir()!, "..");
|
|
216
224
|
const exe = join(runningApp, `${this.#version()!.name}.exe`);
|
|
225
|
+
const script = join(tmpdir(), `mirin-apply-${this.#version()!.name}.ps1`);
|
|
217
226
|
const ps = [
|
|
218
227
|
`$ErrorActionPreference='SilentlyContinue'`,
|
|
219
|
-
//
|
|
228
|
+
// Never let the app dir be this process's cwd (Windows can't delete a cwd).
|
|
229
|
+
`Set-Location $env:TEMP`,
|
|
230
|
+
// Wait for the host to exit (it locks the exe), then a beat for CEF's
|
|
231
|
+
// helper subprocesses to release their file locks (libcef.dll etc.).
|
|
220
232
|
`while (Get-Process -Id ${process.pid} -ErrorAction SilentlyContinue) { Start-Sleep -Milliseconds 200 }`,
|
|
221
|
-
// Give CEF's helper subprocesses a beat to exit and release their file
|
|
222
|
-
// locks (libcef.dll etc.) — they go just after the host.
|
|
223
233
|
`Start-Sleep -Milliseconds 700`,
|
|
224
|
-
// Replace the app folder, retrying while any lingering lock clears
|
|
225
|
-
// (~10s max). Unlike Unix, Windows can't delete a dir that's still a
|
|
226
|
-
// process's cwd or holds an open handle — hence the neutral cwd below.
|
|
234
|
+
// Replace the app folder, retrying ~10s while any lingering lock clears.
|
|
227
235
|
`for ($i=0; $i -lt 50; $i++) { Remove-Item -Recurse -Force ${psq(runningApp)}; if (-not (Test-Path ${psq(runningApp)})) { break }; Start-Sleep -Milliseconds 200 }`,
|
|
228
236
|
`Move-Item -Force ${psq(this.#staged)} ${psq(runningApp)}`,
|
|
229
237
|
`Start-Process -FilePath ${psq(exe)} -WorkingDirectory ${psq(runningApp)}`,
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
238
|
+
`Remove-Item -Force ${psq(script)}`, // self-clean
|
|
239
|
+
].join("\n");
|
|
240
|
+
writeFileSync(script, ps, "utf8");
|
|
241
|
+
// The swap MUST outlive this process. A Bun.spawn child is in our job object
|
|
242
|
+
// and is killed when we quit — even with unref/detached (verified). So spawn
|
|
243
|
+
// the swap via WMI Win32_Process.Create: the WMI service launches it, outside
|
|
244
|
+
// our job, so it survives. `await` the short launcher so the swap process
|
|
245
|
+
// exists before core.quit() (no race), then quit.
|
|
246
|
+
const wmiPath = script.replace(/'/g, "''");
|
|
247
|
+
const wmi =
|
|
248
|
+
`([wmiclass]'Win32_Process').Create('powershell -NoProfile -NonInteractive ` +
|
|
249
|
+
`-WindowStyle Hidden -ExecutionPolicy Bypass -File "${wmiPath}"') | Out-Null`;
|
|
250
|
+
const launcher = Bun.spawn(
|
|
251
|
+
["powershell", "-NoProfile", "-NonInteractive", "-Command", wmi],
|
|
252
|
+
{ stdin: "ignore", stdout: "ignore", stderr: "ignore" },
|
|
253
|
+
);
|
|
254
|
+
await launcher.exited;
|
|
242
255
|
} else {
|
|
243
256
|
const runningApp = join(this.#resourcesDir()!, "..", "..");
|
|
244
257
|
const script = [
|