mirinjs 0.1.0-alpha.7 → 0.1.0-alpha.9

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/updater.ts +55 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirinjs",
3
- "version": "0.1.0-alpha.7",
3
+ "version": "0.1.0-alpha.9",
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",
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 { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync } from "node:fs";
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";
@@ -213,32 +221,59 @@ export class Updater {
213
221
  // to exit (the exe/bundle is locked while running), swaps the folder, relaunches.
214
222
  if (IS_WINDOWS) {
215
223
  const runningApp = join(this.#resourcesDir()!, "..");
216
- const exe = join(runningApp, `${this.#version()!.name}.exe`);
224
+ const name = this.#version()!.name;
225
+ const exe = join(runningApp, `${name}.exe`);
226
+ const script = join(tmpdir(), `mirin-apply-${name}.ps1`);
227
+ const applyVbs = join(tmpdir(), `mirin-apply-${name}.vbs`);
228
+ const launchVbs = join(tmpdir(), `mirin-launch-${name}.vbs`);
217
229
  const ps = [
218
230
  `$ErrorActionPreference='SilentlyContinue'`,
219
- // Wait for the host process to exit (it locks the exe).
231
+ // Never let the app dir be this process's cwd (Windows can't delete a cwd).
232
+ `Set-Location $env:TEMP`,
233
+ // Wait for the host to exit (it locks the exe), then a beat for CEF's
234
+ // helper subprocesses to release their file locks (libcef.dll etc.).
220
235
  `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
236
  `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.
237
+ // Replace the app folder, retrying ~10s while any lingering lock clears.
227
238
  `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
239
  `Move-Item -Force ${psq(this.#staged)} ${psq(runningApp)}`,
229
240
  `Start-Process -FilePath ${psq(exe)} -WorkingDirectory ${psq(runningApp)}`,
230
- ].join("; ");
231
- Bun.spawn(
232
- ["powershell", "-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", ps],
233
- {
234
- // Run from a neutral dir, NOT the inherited install dir — otherwise the
235
- // app folder is this PowerShell's cwd and Windows refuses to delete it.
236
- cwd: tmpdir(),
237
- stdin: "ignore",
238
- stdout: "ignore",
239
- stderr: "ignore",
240
- },
241
- ).unref();
241
+ // self-clean the temp launch scripts (this .ps1 + the two .vbs shims)
242
+ `Remove-Item -Force ${psq(script)},${psq(applyVbs)},${psq(launchVbs)}`,
243
+ ].join("\n");
244
+ writeFileSync(script, ps, "utf8");
245
+
246
+ // No terminal window may appear during the update. powershell.exe is a
247
+ // console-subsystem program: launching it directly (even with
248
+ // -WindowStyle Hidden) flashes a console, because the OS allocates the
249
+ // console before powershell can hide it. wscript.exe is a GUI-subsystem
250
+ // host with no console; its Shell.Run(cmd, 0) starts powershell with
251
+ // SW_HIDE in the initial STARTUPINFO, so no window is ever shown.
252
+ writeFileSync(
253
+ applyVbs,
254
+ `CreateObject("WScript.Shell").Run "powershell -NoProfile -NonInteractive ` +
255
+ `-ExecutionPolicy Bypass -WindowStyle Hidden -File ""${script}""", 0, False\n`,
256
+ "utf8",
257
+ );
258
+
259
+ // The swap MUST outlive this process. A Bun.spawn child is in our job
260
+ // object and is killed when we quit — even with unref/detached (verified).
261
+ // WMI's Win32_Process.Create has the WMI service spawn it, outside our job,
262
+ // so it survives. Issue that WMI call from a console-less wscript host too,
263
+ // so the launcher itself never flashes a window. `await` the short launcher
264
+ // so the swap process exists before core.quit() (no race), then quit.
265
+ writeFileSync(
266
+ launchVbs,
267
+ `GetObject("winmgmts:Win32_Process").Create ` +
268
+ `"wscript.exe //B //Nologo ""${applyVbs}""", Null, Null, pid\n`,
269
+ "utf8",
270
+ );
271
+ const launcher = Bun.spawn(["wscript.exe", "//B", "//Nologo", launchVbs], {
272
+ stdin: "ignore",
273
+ stdout: "ignore",
274
+ stderr: "ignore",
275
+ });
276
+ await launcher.exited;
242
277
  } else {
243
278
  const runningApp = join(this.#resourcesDir()!, "..", "..");
244
279
  const script = [