craft-native 0.0.86 → 0.0.88

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/dist/index.cjs CHANGED
@@ -52,6 +52,7 @@ __export(exports_src, {
52
52
  watch: () => watch,
53
53
  vueOptimizations: () => vueOptimizations,
54
54
  verifyPassword: () => verifyPassword,
55
+ verifyBundleTrust: () => verifyBundleTrust,
55
56
  variants: () => variants,
56
57
  uuid: () => uuid,
57
58
  updater: () => exports_updater,
@@ -70,6 +71,7 @@ __export(exports_src, {
70
71
  tahoeStyles: () => tahoeStyles,
71
72
  tahoeStyle: () => tahoeStyle,
72
73
  tahoeDemoData: () => tahoeDemoData,
74
+ swapBundle: () => swapBundle,
73
75
  svelteOptimizations: () => svelteOptimizations,
74
76
  submenu: () => submenu,
75
77
  styles: () => styles,
@@ -117,6 +119,7 @@ __export(exports_src, {
117
119
  registerShortcut: () => registerShortcut,
118
120
  readText: () => readText,
119
121
  readHTML: () => readHTML,
122
+ readBundleIdentity: () => readBundleIdentity,
120
123
  readBinaryFile: () => readBinaryFile,
121
124
  reactOptimizations: () => reactOptimizations,
122
125
  randomString: () => randomString,
@@ -184,9 +187,13 @@ __export(exports_src, {
184
187
  foregroundService: () => foregroundService,
185
188
  focusFilters: () => focusFilters,
186
189
  focus: () => focus,
190
+ extractBundleFromZip: () => extractBundleFromZip,
191
+ extractBundleFromDmg: () => extractBundleFromDmg,
192
+ extractBundle: () => extractBundle,
187
193
  exit: () => exit,
188
194
  exec: () => exec,
189
195
  env: () => env,
196
+ dittoBundle: () => dittoBundle,
190
197
  dialog: () => dialog,
191
198
  device: () => device,
192
199
  desktopWidgets: () => desktopWidgets,
@@ -217,8 +224,10 @@ __export(exports_src, {
217
224
  commonOptimizations: () => commonOptimizations,
218
225
  clsx: () => cx,
219
226
  clipboard: () => clipboard,
227
+ clearQuarantine: () => clearQuarantine,
220
228
  checkbox: () => checkbox,
221
229
  carplay: () => carplay,
230
+ canReplaceBundle: () => canReplaceBundle,
222
231
  canQuickLook: () => canQuickLook,
223
232
  camera: () => camera,
224
233
  buildMenu: () => buildMenu,
@@ -2532,6 +2541,9 @@ class Window {
2532
2541
  async setVibrancy(vibrancy) {
2533
2542
  await this._call("setVibrancy", { vibrancy });
2534
2543
  }
2544
+ async setAppearance(appearance) {
2545
+ await this._call("setAppearance", { appearance });
2546
+ }
2535
2547
  get windowControls() {
2536
2548
  return globalThis.craft?.windowControls;
2537
2549
  }
@@ -2631,6 +2643,7 @@ class WindowManager {
2631
2643
  setOpacity = (opacity) => this.current.setOpacity(opacity);
2632
2644
  setBackgroundColor = (color) => this.current.setBackgroundColor(color);
2633
2645
  setVibrancy = (vibrancy) => this.current.setVibrancy(vibrancy);
2646
+ setAppearance = (appearance) => this.current.setAppearance(appearance);
2634
2647
  setResizable = (resizable) => this.current.setResizable(resizable);
2635
2648
  startDrag = () => this.current.startDrag();
2636
2649
  getState = () => this.current.getState();
@@ -9979,24 +9992,236 @@ var commonOptimizations = {
9979
9992
  // src/updater/index.ts
9980
9993
  var exports_updater = {};
9981
9994
  __export(exports_updater, {
9995
+ verifyBundleTrust: () => verifyBundleTrust,
9982
9996
  updaterCommand: () => updaterCommand,
9997
+ swapBundle: () => swapBundle,
9998
+ readBundleIdentity: () => readBundleIdentity,
9999
+ isMacOS: () => isMacOS,
9983
10000
  generateUpdateManifest: () => generateUpdateManifest,
10001
+ extractBundleFromZip: () => extractBundleFromZip,
10002
+ extractBundleFromDmg: () => extractBundleFromDmg,
10003
+ extractBundle: () => extractBundle,
10004
+ dittoBundle: () => dittoBundle,
9984
10005
  default: () => updater_default,
10006
+ clearQuarantine: () => clearQuarantine,
10007
+ canReplaceBundle: () => canReplaceBundle,
9985
10008
  DeltaGenerator: () => DeltaGenerator,
9986
10009
  AutoUpdater: () => AutoUpdater
9987
10010
  });
9988
- var import_fs3 = require("fs");
9989
- var import_path2 = require("path");
9990
- var import_child_process2 = require("child_process");
10011
+ var import_fs4 = require("fs");
10012
+ var import_path3 = require("path");
10013
+ var import_os3 = require("os");
10014
+ var import_child_process3 = require("child_process");
9991
10015
  var import_crypto3 = require("crypto");
9992
10016
  var import_events3 = require("events");
9993
- function findFirstByName(root, namePattern, kind) {
9994
- const out = import_child_process2.execFileSync("find", [root, "-name", namePattern, "-type", kind, "-print0"]);
9995
- const nullByte = String.fromCharCode(0);
9996
- const parts = out.toString("utf-8").split(nullByte).filter((s) => s.length > 0);
9997
- return parts[0] ?? "";
10017
+
10018
+ // src/updater/macos-bundle.ts
10019
+ var import_child_process2 = require("child_process");
10020
+ var import_fs3 = require("fs");
10021
+ var import_os2 = require("os");
10022
+ var import_path2 = require("path");
10023
+ var import_util = require("util");
10024
+ var execFileAsync = import_util.promisify(import_child_process2.execFile);
10025
+ function isMacOS() {
10026
+ return process.platform === "darwin";
10027
+ }
10028
+ async function readBundleIdentity(appPath) {
10029
+ const empty = { identifier: null, teamId: null, authority: null };
10030
+ if (!isMacOS())
10031
+ return empty;
10032
+ let report;
10033
+ try {
10034
+ const { stderr, stdout } = await execFileAsync("codesign", ["-dv", "--verbose=4", appPath]);
10035
+ report = `${stderr}
10036
+ ${stdout}`;
10037
+ } catch (error) {
10038
+ const e = error;
10039
+ report = `${e.stderr ?? ""}
10040
+ ${e.stdout ?? ""}`;
10041
+ if (!report.trim())
10042
+ return empty;
10043
+ }
10044
+ const field = (name) => {
10045
+ const match = report.match(new RegExp(`^${name}=(.+)$`, "m"));
10046
+ if (!match)
10047
+ return null;
10048
+ const value = match[1].trim();
10049
+ return value === "not set" || value.length === 0 ? null : value;
10050
+ };
10051
+ return {
10052
+ identifier: field("Identifier"),
10053
+ teamId: field("TeamIdentifier"),
10054
+ authority: field("Authority")
10055
+ };
10056
+ }
10057
+ async function verifyBundleTrust(appPath, policy = {}) {
10058
+ const identity = await readBundleIdentity(appPath);
10059
+ const requireNotarized = policy.requireNotarized ?? true;
10060
+ if (!import_fs3.existsSync(appPath)) {
10061
+ return { ok: false, identity, gatekeeperSource: null, reason: "unreadable", detail: `No such bundle: ${appPath}` };
10062
+ }
10063
+ try {
10064
+ await execFileAsync("codesign", ["--verify", "--deep", "--strict", "--verbose=2", appPath]);
10065
+ } catch (error) {
10066
+ return {
10067
+ ok: false,
10068
+ identity,
10069
+ gatekeeperSource: null,
10070
+ reason: "codesign-invalid",
10071
+ detail: describe(error)
10072
+ };
10073
+ }
10074
+ let gatekeeperSource = null;
10075
+ if (requireNotarized) {
10076
+ try {
10077
+ const { stderr, stdout } = await execFileAsync("spctl", ["-a", "-t", "exec", "-vv", appPath]);
10078
+ const report = `${stderr}
10079
+ ${stdout}`;
10080
+ gatekeeperSource = report.match(/^source=(.+)$/m)?.[1]?.trim() ?? null;
10081
+ } catch (error) {
10082
+ return {
10083
+ ok: false,
10084
+ identity,
10085
+ gatekeeperSource: null,
10086
+ reason: "gatekeeper-rejected",
10087
+ detail: describe(error)
10088
+ };
10089
+ }
10090
+ }
10091
+ if (policy.teamId && identity.teamId !== policy.teamId) {
10092
+ return {
10093
+ ok: false,
10094
+ identity,
10095
+ gatekeeperSource,
10096
+ reason: "team-mismatch",
10097
+ detail: `Expected Team ID ${policy.teamId}, bundle is signed by ${identity.teamId ?? "nobody"}`
10098
+ };
10099
+ }
10100
+ return { ok: true, identity, gatekeeperSource };
10101
+ }
10102
+ async function clearQuarantine(appPath) {
10103
+ if (!isMacOS())
10104
+ return;
10105
+ try {
10106
+ await execFileAsync("xattr", ["-d", "-r", "com.apple.quarantine", appPath]);
10107
+ } catch {}
10108
+ }
10109
+ async function dittoBundle(source, destination) {
10110
+ await execFileAsync("ditto", [source, destination]);
10111
+ }
10112
+ function findTopLevelApp(root) {
10113
+ const out = import_child_process2.execFileSync("find", [root, "-maxdepth", "2", "-name", "*.app", "-type", "d", "-print0"]);
10114
+ const parts = out.toString("utf-8").split("\x00").filter((s) => s.length > 0);
10115
+ parts.sort((a, b) => a.split("/").length - b.split("/").length);
10116
+ return parts[0] ?? null;
10117
+ }
10118
+ async function extractBundleFromDmg(dmgPath, stagingDir) {
10119
+ const staging = stagingDir ?? import_fs3.mkdtempSync(import_path2.join(import_os2.tmpdir(), "craft-update-"));
10120
+ const mountPoint = import_fs3.mkdtempSync(import_path2.join(import_os2.tmpdir(), "craft-mount-"));
10121
+ let mounted = false;
10122
+ try {
10123
+ await execFileAsync("hdiutil", ["attach", dmgPath, "-nobrowse", "-readonly", "-mountpoint", mountPoint]);
10124
+ mounted = true;
10125
+ const source = findTopLevelApp(mountPoint);
10126
+ if (!source)
10127
+ throw new Error(`No .app found in disk image: ${dmgPath}`);
10128
+ const appPath = import_path2.join(staging, import_path2.basename(source));
10129
+ await dittoBundle(source, appPath);
10130
+ return { appPath, stagingDir: staging };
10131
+ } finally {
10132
+ if (mounted) {
10133
+ try {
10134
+ await execFileAsync("hdiutil", ["detach", mountPoint, "-force"]);
10135
+ } catch {}
10136
+ }
10137
+ try {
10138
+ import_fs3.rmSync(mountPoint, { recursive: true, force: true });
10139
+ } catch {}
10140
+ }
10141
+ }
10142
+ async function extractBundleFromZip(zipPath, stagingDir) {
10143
+ const staging = stagingDir ?? import_fs3.mkdtempSync(import_path2.join(import_os2.tmpdir(), "craft-update-"));
10144
+ const unpacked = import_path2.join(staging, "payload");
10145
+ await execFileAsync("ditto", ["-x", "-k", zipPath, unpacked]);
10146
+ const source = findTopLevelApp(unpacked);
10147
+ if (!source)
10148
+ throw new Error(`No .app found in archive: ${zipPath}`);
10149
+ return { appPath: source, stagingDir: staging };
10150
+ }
10151
+ async function extractBundle(archivePath, stagingDir) {
10152
+ if (archivePath.endsWith(".dmg"))
10153
+ return extractBundleFromDmg(archivePath, stagingDir);
10154
+ if (archivePath.endsWith(".zip"))
10155
+ return extractBundleFromZip(archivePath, stagingDir);
10156
+ throw new Error(`Cannot extract an app bundle from ${archivePath}`);
10157
+ }
10158
+ async function swapBundle(stagedPath, installedPath) {
10159
+ if (!import_fs3.existsSync(stagedPath))
10160
+ throw new Error(`Staged bundle is missing: ${stagedPath}`);
10161
+ const parent = import_path2.dirname(installedPath);
10162
+ const stamp = `${Date.now()}.${process.pid}`;
10163
+ const incoming = import_path2.join(parent, `.${import_path2.basename(installedPath)}.${stamp}.incoming`);
10164
+ const retired = import_path2.join(parent, `.${import_path2.basename(installedPath)}.${stamp}.retired`);
10165
+ try {
10166
+ import_fs3.renameSync(stagedPath, incoming);
10167
+ } catch {
10168
+ await dittoBundle(stagedPath, incoming);
10169
+ }
10170
+ const hadPrevious = import_fs3.existsSync(installedPath);
10171
+ try {
10172
+ if (hadPrevious)
10173
+ import_fs3.renameSync(installedPath, retired);
10174
+ try {
10175
+ import_fs3.renameSync(incoming, installedPath);
10176
+ } catch (error) {
10177
+ if (hadPrevious) {
10178
+ try {
10179
+ import_fs3.renameSync(retired, installedPath);
10180
+ } catch {}
10181
+ }
10182
+ throw error;
10183
+ }
10184
+ } catch (error) {
10185
+ try {
10186
+ import_fs3.rmSync(incoming, { recursive: true, force: true });
10187
+ } catch {}
10188
+ throw error;
10189
+ }
10190
+ if (hadPrevious) {
10191
+ try {
10192
+ import_fs3.rmSync(retired, { recursive: true, force: true });
10193
+ return { previousPath: null };
10194
+ } catch {
10195
+ return { previousPath: retired };
10196
+ }
10197
+ }
10198
+ return { previousPath: null };
10199
+ }
10200
+ function canReplaceBundle(installedPath) {
10201
+ try {
10202
+ const parent = import_path2.dirname(installedPath);
10203
+ import_fs3.accessSync(parent, import_fs3.constants.W_OK);
10204
+ if (import_fs3.existsSync(installedPath))
10205
+ import_fs3.statSync(installedPath);
10206
+ return true;
10207
+ } catch {
10208
+ return false;
10209
+ }
10210
+ }
10211
+ function describe(error) {
10212
+ if (error && typeof error === "object") {
10213
+ const e = error;
10214
+ const stderr = e.stderr?.trim();
10215
+ if (stderr)
10216
+ return stderr;
10217
+ if (e.message)
10218
+ return e.message;
10219
+ }
10220
+ return String(error);
9998
10221
  }
9999
10222
 
10223
+ // src/updater/index.ts
10224
+ var PROGRESS_INTERVAL_MS = 100;
10000
10225
  class AutoUpdater extends import_events3.EventEmitter {
10001
10226
  config;
10002
10227
  updateInfo = null;
@@ -10005,12 +10230,22 @@ class AutoUpdater extends import_events3.EventEmitter {
10005
10230
  cachedEtag = null;
10006
10231
  cachedLastModified = null;
10007
10232
  cachedManifest = null;
10233
+ lastError = null;
10008
10234
  emitError(error) {
10235
+ this.lastError = error instanceof Error ? error : new Error(String(error));
10009
10236
  if (this.listenerCount("error") > 0)
10010
10237
  this.emit("error", error);
10011
10238
  else
10012
10239
  console.error("[Updater]", error);
10013
10240
  }
10241
+ downloadDir() {
10242
+ if (this.config.downloadDir)
10243
+ return this.config.downloadDir;
10244
+ return import_path3.join(import_os3.tmpdir(), "craft-updates", import_path3.basename(this.config.appPath) || "app");
10245
+ }
10246
+ getLastError() {
10247
+ return this.lastError;
10248
+ }
10014
10249
  constructor(config) {
10015
10250
  super();
10016
10251
  const url = config.updateUrl;
@@ -10044,6 +10279,7 @@ class AutoUpdater extends import_events3.EventEmitter {
10044
10279
  }
10045
10280
  async checkForUpdates() {
10046
10281
  this.emit("checking-for-update");
10282
+ this.lastError = null;
10047
10283
  try {
10048
10284
  const platform = this.getPlatform();
10049
10285
  const url = `${this.config.updateUrl}?v=${this.config.currentVersion}&channel=${this.config.channel}&platform=${platform}`;
@@ -10092,20 +10328,21 @@ class AutoUpdater extends import_events3.EventEmitter {
10092
10328
  if (!this.updateInfo) {
10093
10329
  throw new Error("No update available");
10094
10330
  }
10331
+ this.lastError = null;
10095
10332
  const platform = this.getPlatform();
10096
10333
  const platformUpdate = this.updateInfo.platforms[platform];
10097
10334
  if (!platformUpdate) {
10098
10335
  throw new Error(`No update available for platform: ${platform}`);
10099
10336
  }
10100
- const deltaUpdate = platformUpdate.delta?.find((d) => d.fromVersion === this.config.currentVersion);
10337
+ const deltaUpdate = DeltaGenerator.isSupported() ? platformUpdate.delta?.find((d) => d.fromVersion === this.config.currentVersion) : undefined;
10101
10338
  const _updateSource = deltaUpdate || platformUpdate;
10102
10339
  const url = deltaUpdate?.url || platformUpdate.url;
10103
10340
  const expectedSize = deltaUpdate?.size || platformUpdate.size;
10104
10341
  const expectedHash = deltaUpdate?.sha256 || platformUpdate.sha256;
10105
- const downloadDir = import_path2.join(this.config.appPath, "..", ".craft-updates");
10106
- import_fs3.mkdirSync(downloadDir, { recursive: true });
10107
- const fileName = import_path2.basename(url);
10108
- this.downloadPath = import_path2.join(downloadDir, fileName);
10342
+ const downloadDir = this.downloadDir();
10343
+ import_fs4.mkdirSync(downloadDir, { recursive: true });
10344
+ const fileName = import_path3.basename(url);
10345
+ this.downloadPath = import_path3.join(downloadDir, fileName);
10109
10346
  try {
10110
10347
  const response = await fetch(url);
10111
10348
  if (!response.ok) {
@@ -10115,63 +10352,83 @@ class AutoUpdater extends import_events3.EventEmitter {
10115
10352
  const total = contentLength || expectedSize;
10116
10353
  let downloaded = 0;
10117
10354
  const startTime = Date.now();
10118
- const fileStream = import_fs3.createWriteStream(this.downloadPath);
10355
+ const fileStream = import_fs4.createWriteStream(this.downloadPath);
10119
10356
  const reader = response.body?.getReader();
10120
10357
  if (!reader) {
10121
10358
  throw new Error("Failed to get response reader");
10122
10359
  }
10360
+ const digest = import_crypto3.createHash("sha256");
10361
+ let lastProgressAt = 0;
10123
10362
  while (true) {
10124
10363
  const { done, value } = await reader.read();
10125
10364
  if (done)
10126
10365
  break;
10127
- fileStream.write(value);
10366
+ digest.update(value);
10367
+ if (!fileStream.write(value)) {
10368
+ await new Promise((resolve, reject) => {
10369
+ const onDrain = () => {
10370
+ fileStream.off("error", onError);
10371
+ resolve();
10372
+ };
10373
+ const onError = (error) => {
10374
+ fileStream.off("drain", onDrain);
10375
+ reject(error);
10376
+ };
10377
+ fileStream.once("drain", onDrain);
10378
+ fileStream.once("error", onError);
10379
+ });
10380
+ }
10128
10381
  downloaded += value.length;
10129
- const elapsed = (Date.now() - startTime) / 1000;
10130
- const speed = downloaded / elapsed;
10131
- const progress = {
10132
- phase: "downloading",
10133
- percent: Math.round(downloaded / total * 100),
10134
- bytesDownloaded: downloaded,
10135
- bytesTotal: total,
10136
- speed
10137
- };
10138
- this.emit("download-progress", progress);
10382
+ const now = Date.now();
10383
+ if (now - lastProgressAt >= PROGRESS_INTERVAL_MS || downloaded === total) {
10384
+ lastProgressAt = now;
10385
+ const elapsed = (now - startTime) / 1000;
10386
+ const speed = elapsed > 0 ? downloaded / elapsed : 0;
10387
+ const progress = {
10388
+ phase: "downloading",
10389
+ percent: total > 0 ? Math.round(downloaded / total * 100) : 0,
10390
+ bytesDownloaded: downloaded,
10391
+ bytesTotal: total,
10392
+ speed
10393
+ };
10394
+ this.emit("download-progress", progress);
10395
+ }
10139
10396
  }
10140
10397
  fileStream.end();
10141
10398
  await new Promise((resolve, reject) => {
10142
10399
  fileStream.once("finish", resolve);
10143
10400
  fileStream.once("error", reject);
10144
10401
  });
10145
- const hash = await this.computeFileHash(this.downloadPath);
10402
+ const hash = digest.digest("hex");
10146
10403
  if (hash !== expectedHash) {
10147
- import_fs3.unlinkSync(this.downloadPath);
10404
+ import_fs4.unlinkSync(this.downloadPath);
10148
10405
  throw new Error("Download verification failed: hash mismatch");
10149
10406
  }
10150
10407
  if (deltaUpdate) {
10151
- if (!import_fs3.statSync(this.config.appPath).isFile()) {
10408
+ if (!import_fs4.statSync(this.config.appPath).isFile()) {
10152
10409
  throw new Error("Delta updates require appPath to point to the installed bundle file");
10153
10410
  }
10154
10411
  const patchPath = this.downloadPath;
10155
- const reconstructedPath = import_path2.join(downloadDir, `reconstructed-${import_path2.basename(platformUpdate.url)}`);
10412
+ const reconstructedPath = import_path3.join(downloadDir, `reconstructed-${import_path3.basename(platformUpdate.url)}`);
10156
10413
  await DeltaGenerator.apply(this.config.appPath, patchPath, reconstructedPath);
10157
10414
  const reconstructedHash = await this.computeFileHash(reconstructedPath);
10158
10415
  if (reconstructedHash !== platformUpdate.sha256) {
10159
10416
  try {
10160
- import_fs3.unlinkSync(reconstructedPath);
10417
+ import_fs4.unlinkSync(reconstructedPath);
10161
10418
  } catch {}
10162
10419
  throw new Error("Delta reconstruction failed: full bundle hash mismatch");
10163
10420
  }
10164
- import_fs3.unlinkSync(patchPath);
10421
+ import_fs4.unlinkSync(patchPath);
10165
10422
  this.downloadPath = reconstructedPath;
10166
10423
  }
10167
10424
  if (this.config.publicKeyPem && !platformUpdate.signature) {
10168
- import_fs3.unlinkSync(this.downloadPath);
10425
+ import_fs4.unlinkSync(this.downloadPath);
10169
10426
  throw new Error("Updater: publicKeyPem is configured but the manifest entry has no signature. " + "Refusing to install unsigned update.");
10170
10427
  }
10171
10428
  if (platformUpdate.signature) {
10172
10429
  const valid = await this.verifySignature(this.downloadPath, platformUpdate.signature);
10173
10430
  if (!valid) {
10174
- import_fs3.unlinkSync(this.downloadPath);
10431
+ import_fs4.unlinkSync(this.downloadPath);
10175
10432
  throw new Error("Download verification failed: invalid signature");
10176
10433
  }
10177
10434
  }
@@ -10190,7 +10447,7 @@ class AutoUpdater extends import_events3.EventEmitter {
10190
10447
  }
10191
10448
  }
10192
10449
  async installUpdate(restartAfter = true) {
10193
- if (!this.downloadPath || !import_fs3.existsSync(this.downloadPath)) {
10450
+ if (!this.downloadPath || !import_fs4.existsSync(this.downloadPath)) {
10194
10451
  throw new Error("No update downloaded");
10195
10452
  }
10196
10453
  if (this.updateInfo) {
@@ -10200,7 +10457,7 @@ class AutoUpdater extends import_events3.EventEmitter {
10200
10457
  const hash = await this.computeFileHash(this.downloadPath);
10201
10458
  if (hash !== platformUpdate.sha256) {
10202
10459
  try {
10203
- import_fs3.unlinkSync(this.downloadPath);
10460
+ import_fs4.unlinkSync(this.downloadPath);
10204
10461
  } catch {}
10205
10462
  throw new Error(`Updater.installUpdate: pre-install hash mismatch on ${this.downloadPath}. ` + "The downloaded bundle changed between download and install — refusing to run installer.");
10206
10463
  }
@@ -10211,7 +10468,7 @@ class AutoUpdater extends import_events3.EventEmitter {
10211
10468
  const valid = await this.verifySignature(this.downloadPath, platformUpdate.signature);
10212
10469
  if (!valid) {
10213
10470
  try {
10214
- import_fs3.unlinkSync(this.downloadPath);
10471
+ import_fs4.unlinkSync(this.downloadPath);
10215
10472
  } catch {}
10216
10473
  throw new Error("Updater.installUpdate: invalid signature on downloaded bundle");
10217
10474
  }
@@ -10234,14 +10491,17 @@ class AutoUpdater extends import_events3.EventEmitter {
10234
10491
  await this.installLinuxUpdate();
10235
10492
  break;
10236
10493
  }
10237
- if (this.downloadPath && import_fs3.existsSync(this.downloadPath)) {
10238
- import_fs3.unlinkSync(this.downloadPath);
10494
+ if (this.downloadPath && import_fs4.existsSync(this.downloadPath)) {
10495
+ import_fs4.unlinkSync(this.downloadPath);
10239
10496
  }
10497
+ try {
10498
+ import_fs4.rmdirSync(this.downloadDir());
10499
+ } catch {}
10240
10500
  progress.phase = "done";
10241
10501
  progress.percent = 100;
10242
10502
  this.emit("download-progress", progress);
10243
10503
  if (restartAfter) {
10244
- this.restartApp();
10504
+ await this.restartApp();
10245
10505
  }
10246
10506
  } catch (error) {
10247
10507
  progress.phase = "error";
@@ -10252,54 +10512,61 @@ class AutoUpdater extends import_events3.EventEmitter {
10252
10512
  async installMacOSUpdate() {
10253
10513
  const downloadPath = this.downloadPath;
10254
10514
  const appPath = this.config.appPath;
10255
- if (downloadPath.endsWith(".zip")) {
10256
- const tempDir2 = import_path2.join(import_path2.dirname(downloadPath), "extracted");
10257
- import_fs3.mkdirSync(tempDir2, { recursive: true });
10258
- import_child_process2.execFileSync("unzip", ["-o", downloadPath, "-d", tempDir2]);
10259
- const extractedApp = findFirstByName(tempDir2, "*.app", "d");
10260
- if (extractedApp) {
10261
- const { rmSync: rmSync3, renameSync } = await import("fs");
10262
- rmSync3(appPath, { recursive: true, force: true });
10263
- renameSync(extractedApp, appPath);
10264
- }
10265
- const { rmSync: rmSync2 } = await import("fs");
10266
- rmSync2(tempDir2, { recursive: true, force: true });
10267
- } else if (downloadPath.endsWith(".dmg")) {
10268
- const mountOutput = import_child_process2.execFileSync("hdiutil", ["attach", downloadPath, "-nobrowse"]).toString();
10269
- const mountPoint = mountOutput.match(/\/Volumes\/[^\n]+/)?.[0];
10270
- if (mountPoint) {
10271
- const dmgApp = findFirstByName(mountPoint, "*.app", "d");
10272
- if (dmgApp) {
10273
- const { rmSync: rmSync2, cpSync: cpSync2 } = await import("fs");
10274
- rmSync2(appPath, { recursive: true, force: true });
10275
- cpSync2(dmgApp, appPath, { recursive: true });
10276
- }
10277
- import_child_process2.execFileSync("hdiutil", ["detach", mountPoint]);
10278
- }
10279
- } else if (downloadPath.endsWith(".pkg")) {
10515
+ if (downloadPath.endsWith(".pkg")) {
10516
+ await this.installMacOSPackage(downloadPath);
10517
+ return;
10518
+ }
10519
+ if (!canReplaceBundle(appPath)) {
10520
+ throw new Error(`Cannot replace ${appPath}: ${import_path3.dirname(appPath)} is not writable by this user. ` + "Install the update manually, or move the app somewhere you own.");
10521
+ }
10522
+ const staged = await extractBundle(downloadPath);
10523
+ try {
10524
+ const trust = await this.assertBundleTrusted(staged.appPath);
10525
+ await clearQuarantine(staged.appPath);
10526
+ const { previousPath } = await swapBundle(staged.appPath, appPath);
10527
+ this.emit("update-installed", {
10528
+ path: appPath,
10529
+ version: this.updateInfo?.version,
10530
+ identity: trust.identity,
10531
+ leftoverPath: previousPath
10532
+ });
10533
+ } finally {
10280
10534
  try {
10281
- import_child_process2.execFileSync("pkgutil", ["--check-signature", downloadPath], { stdio: "pipe" });
10282
- } catch (e) {
10283
- throw new Error(`Refusing to install ${downloadPath}: pkgutil --check-signature failed. The .pkg is not signed by a trusted Apple certificate chain. Underlying error: ${e.message}`);
10284
- }
10285
- import_child_process2.execFileSync("sudo", ["installer", "-pkg", downloadPath, "-target", "/"]);
10535
+ import_fs4.rmSync(staged.stagingDir, { recursive: true, force: true });
10536
+ } catch {}
10537
+ }
10538
+ }
10539
+ async assertBundleTrusted(bundlePath) {
10540
+ const policy = this.config.macos ?? {};
10541
+ const trust = await verifyBundleTrust(bundlePath, policy);
10542
+ if (!trust.ok) {
10543
+ throw new Error(`Refusing to install ${bundlePath}: ${trust.reason} (${trust.detail ?? "no detail"}). ` + "The downloaded bundle is not one macOS will run as this application.");
10544
+ }
10545
+ return trust;
10546
+ }
10547
+ async installMacOSPackage(downloadPath) {
10548
+ try {
10549
+ import_child_process3.execFileSync("pkgutil", ["--check-signature", downloadPath], { stdio: "pipe" });
10550
+ } catch (e) {
10551
+ throw new Error(`Refusing to install ${downloadPath}: pkgutil --check-signature failed. ` + "The .pkg is not signed by a trusted Apple certificate chain. " + `Underlying error: ${e.message}`);
10286
10552
  }
10553
+ import_child_process3.execFileSync("sudo", ["installer", "-pkg", downloadPath, "-target", "/"]);
10287
10554
  }
10288
10555
  async installWindowsUpdate() {
10289
10556
  const downloadPath = this.downloadPath;
10290
10557
  if (downloadPath.endsWith(".exe")) {
10291
- import_child_process2.spawn(downloadPath, ["/S", "/SILENT", "/VERYSILENT"], {
10558
+ import_child_process3.spawn(downloadPath, ["/S", "/SILENT", "/VERYSILENT"], {
10292
10559
  detached: true,
10293
10560
  stdio: "ignore"
10294
10561
  });
10295
10562
  } else if (downloadPath.endsWith(".msi")) {
10296
- import_child_process2.spawn("msiexec", ["/i", downloadPath, "/quiet", "/norestart"], {
10563
+ import_child_process3.spawn("msiexec", ["/i", downloadPath, "/quiet", "/norestart"], {
10297
10564
  detached: true,
10298
10565
  stdio: "ignore"
10299
10566
  });
10300
10567
  } else if (downloadPath.endsWith(".zip")) {
10301
- const appDir = import_path2.dirname(this.config.appPath);
10302
- import_child_process2.execFileSync("powershell", [
10568
+ const appDir = import_path3.dirname(this.config.appPath);
10569
+ import_child_process3.execFileSync("powershell", [
10303
10570
  "-NoProfile",
10304
10571
  "-NonInteractive",
10305
10572
  "-Command",
@@ -10312,30 +10579,31 @@ class AutoUpdater extends import_events3.EventEmitter {
10312
10579
  async installLinuxUpdate() {
10313
10580
  const downloadPath = this.downloadPath;
10314
10581
  if (downloadPath.endsWith(".AppImage")) {
10315
- import_child_process2.execFileSync("chmod", ["+x", downloadPath]);
10316
- const { renameSync } = await import("fs");
10317
- renameSync(downloadPath, this.config.appPath);
10582
+ import_child_process3.execFileSync("chmod", ["+x", downloadPath]);
10583
+ const { renameSync: renameSync2 } = await import("fs");
10584
+ renameSync2(downloadPath, this.config.appPath);
10318
10585
  } else if (downloadPath.endsWith(".deb")) {
10319
- import_child_process2.execFileSync("sudo", ["dpkg", "-i", downloadPath]);
10586
+ import_child_process3.execFileSync("sudo", ["dpkg", "-i", downloadPath]);
10320
10587
  } else if (downloadPath.endsWith(".rpm")) {
10321
- import_child_process2.execFileSync("sudo", ["rpm", "-U", downloadPath]);
10588
+ import_child_process3.execFileSync("sudo", ["rpm", "-U", downloadPath]);
10322
10589
  } else if (downloadPath.endsWith(".tar.gz")) {
10323
- const appDir = import_path2.dirname(this.config.appPath);
10324
- import_child_process2.execFileSync("tar", ["-xzf", downloadPath, "-C", appDir]);
10590
+ const appDir = import_path3.dirname(this.config.appPath);
10591
+ import_child_process3.execFileSync("tar", ["-xzf", downloadPath, "-C", appDir]);
10325
10592
  }
10326
10593
  }
10327
- restartApp() {
10328
- const platform = this.getPlatform();
10594
+ async restartApp() {
10329
10595
  const appPath = this.config.appPath;
10330
- switch (platform) {
10596
+ if (this.config.relaunch) {
10597
+ await this.config.relaunch(appPath);
10598
+ return;
10599
+ }
10600
+ switch (this.getPlatform()) {
10331
10601
  case "darwin":
10332
- import_child_process2.spawn("open", ["-n", appPath], { detached: true, stdio: "ignore" });
10602
+ import_child_process3.spawn("open", ["-n", appPath], { detached: true, stdio: "ignore" });
10333
10603
  break;
10334
10604
  case "win32":
10335
- import_child_process2.spawn(appPath, [], { detached: true, stdio: "ignore" });
10336
- break;
10337
10605
  case "linux":
10338
- import_child_process2.spawn(appPath, [], { detached: true, stdio: "ignore" });
10606
+ import_child_process3.spawn(appPath, [], { detached: true, stdio: "ignore" });
10339
10607
  break;
10340
10608
  }
10341
10609
  process.exit(0);
@@ -10366,7 +10634,7 @@ class AutoUpdater extends import_events3.EventEmitter {
10366
10634
  async computeFileHash(filePath) {
10367
10635
  return new Promise((resolve, reject) => {
10368
10636
  const hash = import_crypto3.createHash("sha256");
10369
- const stream = import_fs3.createReadStream(filePath);
10637
+ const stream = import_fs4.createReadStream(filePath);
10370
10638
  stream.on("data", (chunk) => hash.update(chunk));
10371
10639
  stream.on("end", () => resolve(hash.digest("hex")));
10372
10640
  stream.on("error", reject);
@@ -10391,7 +10659,7 @@ class AutoUpdater extends import_events3.EventEmitter {
10391
10659
  } catch {
10392
10660
  return false;
10393
10661
  }
10394
- const data = import_fs3.readFileSync(filePath);
10662
+ const data = import_fs4.readFileSync(filePath);
10395
10663
  if (algo === "ed25519") {
10396
10664
  return verify(null, data, publicKey, sigBytes);
10397
10665
  }
@@ -10409,18 +10677,32 @@ class AutoUpdater extends import_events3.EventEmitter {
10409
10677
  }
10410
10678
 
10411
10679
  class DeltaGenerator {
10680
+ static supported = null;
10681
+ static isSupported() {
10682
+ if (DeltaGenerator.supported !== null)
10683
+ return DeltaGenerator.supported;
10684
+ DeltaGenerator.supported = ["bspatch", "xdelta3"].some((tool) => {
10685
+ try {
10686
+ import_child_process3.execFileSync("command", ["-v", tool], { stdio: "ignore", shell: true });
10687
+ return true;
10688
+ } catch {
10689
+ return false;
10690
+ }
10691
+ });
10692
+ return DeltaGenerator.supported;
10693
+ }
10412
10694
  static async generate(oldPath, newPath, outputPath) {
10413
10695
  try {
10414
- import_child_process2.execFileSync("bsdiff", [oldPath, newPath, outputPath]);
10415
- const stats = import_fs3.statSync(outputPath);
10696
+ import_child_process3.execFileSync("bsdiff", [oldPath, newPath, outputPath]);
10697
+ const stats = import_fs4.statSync(outputPath);
10416
10698
  const hash = await DeltaGenerator.computeHash(outputPath);
10417
10699
  return {
10418
10700
  size: stats.size,
10419
10701
  sha256: hash
10420
10702
  };
10421
10703
  } catch {
10422
- import_child_process2.execFileSync("xdelta3", ["-e", "-s", oldPath, newPath, outputPath]);
10423
- const stats = import_fs3.statSync(outputPath);
10704
+ import_child_process3.execFileSync("xdelta3", ["-e", "-s", oldPath, newPath, outputPath]);
10705
+ const stats = import_fs4.statSync(outputPath);
10424
10706
  const hash = await DeltaGenerator.computeHash(outputPath);
10425
10707
  return {
10426
10708
  size: stats.size,
@@ -10430,15 +10712,15 @@ class DeltaGenerator {
10430
10712
  }
10431
10713
  static async apply(basePath, deltaPath, outputPath) {
10432
10714
  try {
10433
- import_child_process2.execFileSync("bspatch", [basePath, outputPath, deltaPath]);
10715
+ import_child_process3.execFileSync("bspatch", [basePath, outputPath, deltaPath]);
10434
10716
  } catch {
10435
- import_child_process2.execFileSync("xdelta3", ["-d", "-s", basePath, deltaPath, outputPath]);
10717
+ import_child_process3.execFileSync("xdelta3", ["-d", "-s", basePath, deltaPath, outputPath]);
10436
10718
  }
10437
10719
  }
10438
10720
  static async computeHash(filePath) {
10439
10721
  return new Promise((resolve, reject) => {
10440
10722
  const hash = import_crypto3.createHash("sha256");
10441
- const stream = import_fs3.createReadStream(filePath);
10723
+ const stream = import_fs4.createReadStream(filePath);
10442
10724
  stream.on("data", (chunk) => hash.update(chunk));
10443
10725
  stream.on("end", () => resolve(hash.digest("hex")));
10444
10726
  stream.on("error", reject);
@@ -10455,7 +10737,7 @@ function generateUpdateManifest(options) {
10455
10737
  for (const [platform, info] of Object.entries(options.platforms)) {
10456
10738
  if (!info)
10457
10739
  continue;
10458
- const bytes = import_fs3.readFileSync(info.path);
10740
+ const bytes = import_fs4.readFileSync(info.path);
10459
10741
  const hash = import_crypto3.createHash("sha256").update(bytes).digest("hex");
10460
10742
  manifest.platforms[platform] = {
10461
10743
  url: info.url,
@@ -10465,7 +10747,7 @@ function generateUpdateManifest(options) {
10465
10747
  };
10466
10748
  const platformDeltas = options.deltas?.filter((d) => d.platform === platform) || [];
10467
10749
  for (const delta of platformDeltas) {
10468
- const deltaBytes = import_fs3.readFileSync(delta.path);
10750
+ const deltaBytes = import_fs4.readFileSync(delta.path);
10469
10751
  const deltaHash = import_crypto3.createHash("sha256").update(deltaBytes).digest("hex");
10470
10752
  manifest.platforms[platform].delta.push({
10471
10753
  fromVersion: delta.fromVersion,
@@ -10534,7 +10816,7 @@ ${update.releaseNotes}`);
10534
10816
  releaseDate: new Date().toISOString(),
10535
10817
  platforms: {}
10536
10818
  };
10537
- import_fs3.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
10819
+ import_fs4.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
10538
10820
  console.log(`Manifest generated: ${outputPath}`);
10539
10821
  console.log("Edit the file to add platform-specific update URLs");
10540
10822
  break;
@@ -10560,7 +10842,7 @@ Examples:
10560
10842
  var updater_default = AutoUpdater;
10561
10843
 
10562
10844
  // src/index.ts
10563
- var execFileAsync = import_node_util.promisify(import_node_child_process.execFile);
10845
+ var execFileAsync2 = import_node_util.promisify(import_node_child_process.execFile);
10564
10846
  var versionMismatchWarned = false;
10565
10847
  function getSdkVersion() {
10566
10848
  try {
@@ -10574,7 +10856,7 @@ async function probeBinaryVersion(craftPath) {
10574
10856
  if (versionMismatchWarned)
10575
10857
  return;
10576
10858
  try {
10577
- const { stdout } = await execFileAsync(craftPath, ["--version"], { timeout: 2000 });
10859
+ const { stdout } = await execFileAsync2(craftPath, ["--version"], { timeout: 2000 });
10578
10860
  const nativeVersion = parseCraftVersionOutput(stdout);
10579
10861
  const sdkVersion = getSdkVersion();
10580
10862
  if (sdkVersion !== "unknown" && nativeVersion && nativeVersion !== sdkVersion) {
@@ -10749,7 +11031,9 @@ class CraftApp {
10749
11031
  args.push("--keep-running");
10750
11032
  else if (window3?.keepRunning === false)
10751
11033
  args.push("--quit-on-close");
10752
- if (window3?.webSidebarMaterial) {
11034
+ if (window3?.webWindowMaterial) {
11035
+ args.push("--web-window-material");
11036
+ } else if (window3?.webSidebarMaterial) {
10753
11037
  args.push("--web-sidebar-material");
10754
11038
  if (window3?.webSidebarWidth)
10755
11039
  args.push("--web-sidebar-width", String(window3.webSidebarWidth));