craft-native 0.0.87 → 0.0.89

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.js CHANGED
@@ -16,11 +16,11 @@ var __export = (target, all) => {
16
16
  var __require = import.meta.require;
17
17
 
18
18
  // src/index.ts
19
- import { execFile, spawn as spawn4 } from "child_process";
20
- import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
21
- import { tmpdir as tmpdir2 } from "os";
22
- import { join as join3 } from "path";
23
- import { promisify } from "util";
19
+ import { execFile as execFile2, spawn as spawn4 } from "child_process";
20
+ import { mkdtempSync as mkdtempSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
21
+ import { tmpdir as tmpdir4 } from "os";
22
+ import { join as join4 } from "path";
23
+ import { promisify as promisify2 } from "util";
24
24
 
25
25
  // src/binary-resolver.ts
26
26
  import { existsSync } from "fs";
@@ -2373,9 +2373,12 @@ class WindowManager {
2373
2373
  return this._windows.get(id);
2374
2374
  }
2375
2375
  async create(options = {}) {
2376
- const id = `window_${++this._idCounter}_${Date.now()}`;
2376
+ const id = options.id || `window_${++this._idCounter}_${Date.now()}`;
2377
+ const existing = this._windows.get(id);
2377
2378
  const bridge = getBridge();
2378
- await bridge.request("window.create", { id, ...options });
2379
+ await bridge.request("window.create", { ...options, id });
2380
+ if (existing)
2381
+ return existing;
2379
2382
  const win = new Window(id);
2380
2383
  this._windows.set(id, win);
2381
2384
  return win;
@@ -9753,24 +9756,236 @@ var commonOptimizations = {
9753
9756
  // src/updater/index.ts
9754
9757
  var exports_updater = {};
9755
9758
  __export(exports_updater, {
9759
+ verifyBundleTrust: () => verifyBundleTrust,
9756
9760
  updaterCommand: () => updaterCommand,
9761
+ swapBundle: () => swapBundle,
9762
+ readBundleIdentity: () => readBundleIdentity,
9763
+ isMacOS: () => isMacOS,
9757
9764
  generateUpdateManifest: () => generateUpdateManifest,
9765
+ extractBundleFromZip: () => extractBundleFromZip,
9766
+ extractBundleFromDmg: () => extractBundleFromDmg,
9767
+ extractBundle: () => extractBundle,
9768
+ dittoBundle: () => dittoBundle,
9758
9769
  default: () => updater_default,
9770
+ clearQuarantine: () => clearQuarantine,
9771
+ canReplaceBundle: () => canReplaceBundle,
9759
9772
  DeltaGenerator: () => DeltaGenerator,
9760
9773
  AutoUpdater: () => AutoUpdater
9761
9774
  });
9762
- import { createReadStream, createWriteStream, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, statSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
9763
- import { join as join2, basename as basename2, dirname } from "path";
9764
- import { execFileSync, spawn as spawn3 } from "child_process";
9775
+ import { createReadStream, createWriteStream, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmdirSync, rmSync as rmSync3, statSync as statSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
9776
+ import { join as join3, basename as basename3, dirname as dirname2 } from "path";
9777
+ import { tmpdir as tmpdir3 } from "os";
9778
+ import { execFileSync as execFileSync2, spawn as spawn3 } from "child_process";
9765
9779
  import { createHash as createHash2 } from "crypto";
9766
9780
  import { EventEmitter as EventEmitter3 } from "events";
9767
- function findFirstByName(root, namePattern, kind) {
9768
- const out = execFileSync("find", [root, "-name", namePattern, "-type", kind, "-print0"]);
9769
- const nullByte = String.fromCharCode(0);
9770
- const parts = out.toString("utf-8").split(nullByte).filter((s) => s.length > 0);
9771
- return parts[0] ?? "";
9781
+
9782
+ // src/updater/macos-bundle.ts
9783
+ import { execFile, execFileSync } from "child_process";
9784
+ import { accessSync, constants, existsSync as existsSync3, mkdtempSync as mkdtempSync2, renameSync, rmSync as rmSync2, statSync } from "fs";
9785
+ import { tmpdir as tmpdir2 } from "os";
9786
+ import { basename as basename2, dirname, join as join2 } from "path";
9787
+ import { promisify } from "util";
9788
+ var execFileAsync = promisify(execFile);
9789
+ function isMacOS() {
9790
+ return process.platform === "darwin";
9791
+ }
9792
+ async function readBundleIdentity(appPath) {
9793
+ const empty = { identifier: null, teamId: null, authority: null };
9794
+ if (!isMacOS())
9795
+ return empty;
9796
+ let report;
9797
+ try {
9798
+ const { stderr, stdout } = await execFileAsync("codesign", ["-dv", "--verbose=4", appPath]);
9799
+ report = `${stderr}
9800
+ ${stdout}`;
9801
+ } catch (error) {
9802
+ const e = error;
9803
+ report = `${e.stderr ?? ""}
9804
+ ${e.stdout ?? ""}`;
9805
+ if (!report.trim())
9806
+ return empty;
9807
+ }
9808
+ const field = (name) => {
9809
+ const match = report.match(new RegExp(`^${name}=(.+)$`, "m"));
9810
+ if (!match)
9811
+ return null;
9812
+ const value = match[1].trim();
9813
+ return value === "not set" || value.length === 0 ? null : value;
9814
+ };
9815
+ return {
9816
+ identifier: field("Identifier"),
9817
+ teamId: field("TeamIdentifier"),
9818
+ authority: field("Authority")
9819
+ };
9820
+ }
9821
+ async function verifyBundleTrust(appPath, policy = {}) {
9822
+ const identity = await readBundleIdentity(appPath);
9823
+ const requireNotarized = policy.requireNotarized ?? true;
9824
+ if (!existsSync3(appPath)) {
9825
+ return { ok: false, identity, gatekeeperSource: null, reason: "unreadable", detail: `No such bundle: ${appPath}` };
9826
+ }
9827
+ try {
9828
+ await execFileAsync("codesign", ["--verify", "--deep", "--strict", "--verbose=2", appPath]);
9829
+ } catch (error) {
9830
+ return {
9831
+ ok: false,
9832
+ identity,
9833
+ gatekeeperSource: null,
9834
+ reason: "codesign-invalid",
9835
+ detail: describe(error)
9836
+ };
9837
+ }
9838
+ let gatekeeperSource = null;
9839
+ if (requireNotarized) {
9840
+ try {
9841
+ const { stderr, stdout } = await execFileAsync("spctl", ["-a", "-t", "exec", "-vv", appPath]);
9842
+ const report = `${stderr}
9843
+ ${stdout}`;
9844
+ gatekeeperSource = report.match(/^source=(.+)$/m)?.[1]?.trim() ?? null;
9845
+ } catch (error) {
9846
+ return {
9847
+ ok: false,
9848
+ identity,
9849
+ gatekeeperSource: null,
9850
+ reason: "gatekeeper-rejected",
9851
+ detail: describe(error)
9852
+ };
9853
+ }
9854
+ }
9855
+ if (policy.teamId && identity.teamId !== policy.teamId) {
9856
+ return {
9857
+ ok: false,
9858
+ identity,
9859
+ gatekeeperSource,
9860
+ reason: "team-mismatch",
9861
+ detail: `Expected Team ID ${policy.teamId}, bundle is signed by ${identity.teamId ?? "nobody"}`
9862
+ };
9863
+ }
9864
+ return { ok: true, identity, gatekeeperSource };
9865
+ }
9866
+ async function clearQuarantine(appPath) {
9867
+ if (!isMacOS())
9868
+ return;
9869
+ try {
9870
+ await execFileAsync("xattr", ["-d", "-r", "com.apple.quarantine", appPath]);
9871
+ } catch {}
9872
+ }
9873
+ async function dittoBundle(source, destination) {
9874
+ await execFileAsync("ditto", [source, destination]);
9875
+ }
9876
+ function findTopLevelApp(root) {
9877
+ const out = execFileSync("find", [root, "-maxdepth", "2", "-name", "*.app", "-type", "d", "-print0"]);
9878
+ const parts = out.toString("utf-8").split("\x00").filter((s) => s.length > 0);
9879
+ parts.sort((a, b) => a.split("/").length - b.split("/").length);
9880
+ return parts[0] ?? null;
9881
+ }
9882
+ async function extractBundleFromDmg(dmgPath, stagingDir) {
9883
+ const staging = stagingDir ?? mkdtempSync2(join2(tmpdir2(), "craft-update-"));
9884
+ const mountPoint = mkdtempSync2(join2(tmpdir2(), "craft-mount-"));
9885
+ let mounted = false;
9886
+ try {
9887
+ await execFileAsync("hdiutil", ["attach", dmgPath, "-nobrowse", "-readonly", "-mountpoint", mountPoint]);
9888
+ mounted = true;
9889
+ const source = findTopLevelApp(mountPoint);
9890
+ if (!source)
9891
+ throw new Error(`No .app found in disk image: ${dmgPath}`);
9892
+ const appPath = join2(staging, basename2(source));
9893
+ await dittoBundle(source, appPath);
9894
+ return { appPath, stagingDir: staging };
9895
+ } finally {
9896
+ if (mounted) {
9897
+ try {
9898
+ await execFileAsync("hdiutil", ["detach", mountPoint, "-force"]);
9899
+ } catch {}
9900
+ }
9901
+ try {
9902
+ rmSync2(mountPoint, { recursive: true, force: true });
9903
+ } catch {}
9904
+ }
9905
+ }
9906
+ async function extractBundleFromZip(zipPath, stagingDir) {
9907
+ const staging = stagingDir ?? mkdtempSync2(join2(tmpdir2(), "craft-update-"));
9908
+ const unpacked = join2(staging, "payload");
9909
+ await execFileAsync("ditto", ["-x", "-k", zipPath, unpacked]);
9910
+ const source = findTopLevelApp(unpacked);
9911
+ if (!source)
9912
+ throw new Error(`No .app found in archive: ${zipPath}`);
9913
+ return { appPath: source, stagingDir: staging };
9914
+ }
9915
+ async function extractBundle(archivePath, stagingDir) {
9916
+ if (archivePath.endsWith(".dmg"))
9917
+ return extractBundleFromDmg(archivePath, stagingDir);
9918
+ if (archivePath.endsWith(".zip"))
9919
+ return extractBundleFromZip(archivePath, stagingDir);
9920
+ throw new Error(`Cannot extract an app bundle from ${archivePath}`);
9921
+ }
9922
+ async function swapBundle(stagedPath, installedPath) {
9923
+ if (!existsSync3(stagedPath))
9924
+ throw new Error(`Staged bundle is missing: ${stagedPath}`);
9925
+ const parent = dirname(installedPath);
9926
+ const stamp = `${Date.now()}.${process.pid}`;
9927
+ const incoming = join2(parent, `.${basename2(installedPath)}.${stamp}.incoming`);
9928
+ const retired = join2(parent, `.${basename2(installedPath)}.${stamp}.retired`);
9929
+ try {
9930
+ renameSync(stagedPath, incoming);
9931
+ } catch {
9932
+ await dittoBundle(stagedPath, incoming);
9933
+ }
9934
+ const hadPrevious = existsSync3(installedPath);
9935
+ try {
9936
+ if (hadPrevious)
9937
+ renameSync(installedPath, retired);
9938
+ try {
9939
+ renameSync(incoming, installedPath);
9940
+ } catch (error) {
9941
+ if (hadPrevious) {
9942
+ try {
9943
+ renameSync(retired, installedPath);
9944
+ } catch {}
9945
+ }
9946
+ throw error;
9947
+ }
9948
+ } catch (error) {
9949
+ try {
9950
+ rmSync2(incoming, { recursive: true, force: true });
9951
+ } catch {}
9952
+ throw error;
9953
+ }
9954
+ if (hadPrevious) {
9955
+ try {
9956
+ rmSync2(retired, { recursive: true, force: true });
9957
+ return { previousPath: null };
9958
+ } catch {
9959
+ return { previousPath: retired };
9960
+ }
9961
+ }
9962
+ return { previousPath: null };
9963
+ }
9964
+ function canReplaceBundle(installedPath) {
9965
+ try {
9966
+ const parent = dirname(installedPath);
9967
+ accessSync(parent, constants.W_OK);
9968
+ if (existsSync3(installedPath))
9969
+ statSync(installedPath);
9970
+ return true;
9971
+ } catch {
9972
+ return false;
9973
+ }
9974
+ }
9975
+ function describe(error) {
9976
+ if (error && typeof error === "object") {
9977
+ const e = error;
9978
+ const stderr = e.stderr?.trim();
9979
+ if (stderr)
9980
+ return stderr;
9981
+ if (e.message)
9982
+ return e.message;
9983
+ }
9984
+ return String(error);
9772
9985
  }
9773
9986
 
9987
+ // src/updater/index.ts
9988
+ var PROGRESS_INTERVAL_MS = 100;
9774
9989
  class AutoUpdater extends EventEmitter3 {
9775
9990
  config;
9776
9991
  updateInfo = null;
@@ -9779,12 +9994,22 @@ class AutoUpdater extends EventEmitter3 {
9779
9994
  cachedEtag = null;
9780
9995
  cachedLastModified = null;
9781
9996
  cachedManifest = null;
9997
+ lastError = null;
9782
9998
  emitError(error) {
9999
+ this.lastError = error instanceof Error ? error : new Error(String(error));
9783
10000
  if (this.listenerCount("error") > 0)
9784
10001
  this.emit("error", error);
9785
10002
  else
9786
10003
  console.error("[Updater]", error);
9787
10004
  }
10005
+ downloadDir() {
10006
+ if (this.config.downloadDir)
10007
+ return this.config.downloadDir;
10008
+ return join3(tmpdir3(), "craft-updates", basename3(this.config.appPath) || "app");
10009
+ }
10010
+ getLastError() {
10011
+ return this.lastError;
10012
+ }
9788
10013
  constructor(config) {
9789
10014
  super();
9790
10015
  const url = config.updateUrl;
@@ -9818,6 +10043,7 @@ class AutoUpdater extends EventEmitter3 {
9818
10043
  }
9819
10044
  async checkForUpdates() {
9820
10045
  this.emit("checking-for-update");
10046
+ this.lastError = null;
9821
10047
  try {
9822
10048
  const platform = this.getPlatform();
9823
10049
  const url = `${this.config.updateUrl}?v=${this.config.currentVersion}&channel=${this.config.channel}&platform=${platform}`;
@@ -9866,20 +10092,21 @@ class AutoUpdater extends EventEmitter3 {
9866
10092
  if (!this.updateInfo) {
9867
10093
  throw new Error("No update available");
9868
10094
  }
10095
+ this.lastError = null;
9869
10096
  const platform = this.getPlatform();
9870
10097
  const platformUpdate = this.updateInfo.platforms[platform];
9871
10098
  if (!platformUpdate) {
9872
10099
  throw new Error(`No update available for platform: ${platform}`);
9873
10100
  }
9874
- const deltaUpdate = platformUpdate.delta?.find((d) => d.fromVersion === this.config.currentVersion);
10101
+ const deltaUpdate = DeltaGenerator.isSupported() ? platformUpdate.delta?.find((d) => d.fromVersion === this.config.currentVersion) : undefined;
9875
10102
  const _updateSource = deltaUpdate || platformUpdate;
9876
10103
  const url = deltaUpdate?.url || platformUpdate.url;
9877
10104
  const expectedSize = deltaUpdate?.size || platformUpdate.size;
9878
10105
  const expectedHash = deltaUpdate?.sha256 || platformUpdate.sha256;
9879
- const downloadDir = join2(this.config.appPath, "..", ".craft-updates");
10106
+ const downloadDir = this.downloadDir();
9880
10107
  mkdirSync2(downloadDir, { recursive: true });
9881
- const fileName = basename2(url);
9882
- this.downloadPath = join2(downloadDir, fileName);
10108
+ const fileName = basename3(url);
10109
+ this.downloadPath = join3(downloadDir, fileName);
9883
10110
  try {
9884
10111
  const response = await fetch(url);
9885
10112
  if (!response.ok) {
@@ -9894,39 +10121,59 @@ class AutoUpdater extends EventEmitter3 {
9894
10121
  if (!reader) {
9895
10122
  throw new Error("Failed to get response reader");
9896
10123
  }
10124
+ const digest = createHash2("sha256");
10125
+ let lastProgressAt = 0;
9897
10126
  while (true) {
9898
10127
  const { done, value } = await reader.read();
9899
10128
  if (done)
9900
10129
  break;
9901
- fileStream.write(value);
10130
+ digest.update(value);
10131
+ if (!fileStream.write(value)) {
10132
+ await new Promise((resolve, reject) => {
10133
+ const onDrain = () => {
10134
+ fileStream.off("error", onError);
10135
+ resolve();
10136
+ };
10137
+ const onError = (error) => {
10138
+ fileStream.off("drain", onDrain);
10139
+ reject(error);
10140
+ };
10141
+ fileStream.once("drain", onDrain);
10142
+ fileStream.once("error", onError);
10143
+ });
10144
+ }
9902
10145
  downloaded += value.length;
9903
- const elapsed = (Date.now() - startTime) / 1000;
9904
- const speed = downloaded / elapsed;
9905
- const progress = {
9906
- phase: "downloading",
9907
- percent: Math.round(downloaded / total * 100),
9908
- bytesDownloaded: downloaded,
9909
- bytesTotal: total,
9910
- speed
9911
- };
9912
- this.emit("download-progress", progress);
10146
+ const now = Date.now();
10147
+ if (now - lastProgressAt >= PROGRESS_INTERVAL_MS || downloaded === total) {
10148
+ lastProgressAt = now;
10149
+ const elapsed = (now - startTime) / 1000;
10150
+ const speed = elapsed > 0 ? downloaded / elapsed : 0;
10151
+ const progress = {
10152
+ phase: "downloading",
10153
+ percent: total > 0 ? Math.round(downloaded / total * 100) : 0,
10154
+ bytesDownloaded: downloaded,
10155
+ bytesTotal: total,
10156
+ speed
10157
+ };
10158
+ this.emit("download-progress", progress);
10159
+ }
9913
10160
  }
9914
10161
  fileStream.end();
9915
10162
  await new Promise((resolve, reject) => {
9916
10163
  fileStream.once("finish", resolve);
9917
10164
  fileStream.once("error", reject);
9918
10165
  });
9919
- const hash = await this.computeFileHash(this.downloadPath);
10166
+ const hash = digest.digest("hex");
9920
10167
  if (hash !== expectedHash) {
9921
10168
  unlinkSync(this.downloadPath);
9922
10169
  throw new Error("Download verification failed: hash mismatch");
9923
10170
  }
9924
10171
  if (deltaUpdate) {
9925
- if (!statSync(this.config.appPath).isFile()) {
10172
+ if (!statSync2(this.config.appPath).isFile()) {
9926
10173
  throw new Error("Delta updates require appPath to point to the installed bundle file");
9927
10174
  }
9928
10175
  const patchPath = this.downloadPath;
9929
- const reconstructedPath = join2(downloadDir, `reconstructed-${basename2(platformUpdate.url)}`);
10176
+ const reconstructedPath = join3(downloadDir, `reconstructed-${basename3(platformUpdate.url)}`);
9930
10177
  await DeltaGenerator.apply(this.config.appPath, patchPath, reconstructedPath);
9931
10178
  const reconstructedHash = await this.computeFileHash(reconstructedPath);
9932
10179
  if (reconstructedHash !== platformUpdate.sha256) {
@@ -9964,7 +10211,7 @@ class AutoUpdater extends EventEmitter3 {
9964
10211
  }
9965
10212
  }
9966
10213
  async installUpdate(restartAfter = true) {
9967
- if (!this.downloadPath || !existsSync3(this.downloadPath)) {
10214
+ if (!this.downloadPath || !existsSync4(this.downloadPath)) {
9968
10215
  throw new Error("No update downloaded");
9969
10216
  }
9970
10217
  if (this.updateInfo) {
@@ -10008,14 +10255,17 @@ class AutoUpdater extends EventEmitter3 {
10008
10255
  await this.installLinuxUpdate();
10009
10256
  break;
10010
10257
  }
10011
- if (this.downloadPath && existsSync3(this.downloadPath)) {
10258
+ if (this.downloadPath && existsSync4(this.downloadPath)) {
10012
10259
  unlinkSync(this.downloadPath);
10013
10260
  }
10261
+ try {
10262
+ rmdirSync(this.downloadDir());
10263
+ } catch {}
10014
10264
  progress.phase = "done";
10015
10265
  progress.percent = 100;
10016
10266
  this.emit("download-progress", progress);
10017
10267
  if (restartAfter) {
10018
- this.restartApp();
10268
+ await this.restartApp();
10019
10269
  }
10020
10270
  } catch (error) {
10021
10271
  progress.phase = "error";
@@ -10026,38 +10276,45 @@ class AutoUpdater extends EventEmitter3 {
10026
10276
  async installMacOSUpdate() {
10027
10277
  const downloadPath = this.downloadPath;
10028
10278
  const appPath = this.config.appPath;
10029
- if (downloadPath.endsWith(".zip")) {
10030
- const tempDir2 = join2(dirname(downloadPath), "extracted");
10031
- mkdirSync2(tempDir2, { recursive: true });
10032
- execFileSync("unzip", ["-o", downloadPath, "-d", tempDir2]);
10033
- const extractedApp = findFirstByName(tempDir2, "*.app", "d");
10034
- if (extractedApp) {
10035
- const { rmSync: rmSync3, renameSync } = await import("fs");
10036
- rmSync3(appPath, { recursive: true, force: true });
10037
- renameSync(extractedApp, appPath);
10038
- }
10039
- const { rmSync: rmSync2 } = await import("fs");
10040
- rmSync2(tempDir2, { recursive: true, force: true });
10041
- } else if (downloadPath.endsWith(".dmg")) {
10042
- const mountOutput = execFileSync("hdiutil", ["attach", downloadPath, "-nobrowse"]).toString();
10043
- const mountPoint = mountOutput.match(/\/Volumes\/[^\n]+/)?.[0];
10044
- if (mountPoint) {
10045
- const dmgApp = findFirstByName(mountPoint, "*.app", "d");
10046
- if (dmgApp) {
10047
- const { rmSync: rmSync2, cpSync: cpSync2 } = await import("fs");
10048
- rmSync2(appPath, { recursive: true, force: true });
10049
- cpSync2(dmgApp, appPath, { recursive: true });
10050
- }
10051
- execFileSync("hdiutil", ["detach", mountPoint]);
10052
- }
10053
- } else if (downloadPath.endsWith(".pkg")) {
10279
+ if (downloadPath.endsWith(".pkg")) {
10280
+ await this.installMacOSPackage(downloadPath);
10281
+ return;
10282
+ }
10283
+ if (!canReplaceBundle(appPath)) {
10284
+ throw new Error(`Cannot replace ${appPath}: ${dirname2(appPath)} is not writable by this user. ` + "Install the update manually, or move the app somewhere you own.");
10285
+ }
10286
+ const staged = await extractBundle(downloadPath);
10287
+ try {
10288
+ const trust = await this.assertBundleTrusted(staged.appPath);
10289
+ await clearQuarantine(staged.appPath);
10290
+ const { previousPath } = await swapBundle(staged.appPath, appPath);
10291
+ this.emit("update-installed", {
10292
+ path: appPath,
10293
+ version: this.updateInfo?.version,
10294
+ identity: trust.identity,
10295
+ leftoverPath: previousPath
10296
+ });
10297
+ } finally {
10054
10298
  try {
10055
- execFileSync("pkgutil", ["--check-signature", downloadPath], { stdio: "pipe" });
10056
- } catch (e) {
10057
- 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}`);
10058
- }
10059
- execFileSync("sudo", ["installer", "-pkg", downloadPath, "-target", "/"]);
10299
+ rmSync3(staged.stagingDir, { recursive: true, force: true });
10300
+ } catch {}
10301
+ }
10302
+ }
10303
+ async assertBundleTrusted(bundlePath) {
10304
+ const policy = this.config.macos ?? {};
10305
+ const trust = await verifyBundleTrust(bundlePath, policy);
10306
+ if (!trust.ok) {
10307
+ 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.");
10308
+ }
10309
+ return trust;
10310
+ }
10311
+ async installMacOSPackage(downloadPath) {
10312
+ try {
10313
+ execFileSync2("pkgutil", ["--check-signature", downloadPath], { stdio: "pipe" });
10314
+ } catch (e) {
10315
+ 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}`);
10060
10316
  }
10317
+ execFileSync2("sudo", ["installer", "-pkg", downloadPath, "-target", "/"]);
10061
10318
  }
10062
10319
  async installWindowsUpdate() {
10063
10320
  const downloadPath = this.downloadPath;
@@ -10072,8 +10329,8 @@ class AutoUpdater extends EventEmitter3 {
10072
10329
  stdio: "ignore"
10073
10330
  });
10074
10331
  } else if (downloadPath.endsWith(".zip")) {
10075
- const appDir = dirname(this.config.appPath);
10076
- execFileSync("powershell", [
10332
+ const appDir = dirname2(this.config.appPath);
10333
+ execFileSync2("powershell", [
10077
10334
  "-NoProfile",
10078
10335
  "-NonInteractive",
10079
10336
  "-Command",
@@ -10086,28 +10343,29 @@ class AutoUpdater extends EventEmitter3 {
10086
10343
  async installLinuxUpdate() {
10087
10344
  const downloadPath = this.downloadPath;
10088
10345
  if (downloadPath.endsWith(".AppImage")) {
10089
- execFileSync("chmod", ["+x", downloadPath]);
10090
- const { renameSync } = await import("fs");
10091
- renameSync(downloadPath, this.config.appPath);
10346
+ execFileSync2("chmod", ["+x", downloadPath]);
10347
+ const { renameSync: renameSync2 } = await import("fs");
10348
+ renameSync2(downloadPath, this.config.appPath);
10092
10349
  } else if (downloadPath.endsWith(".deb")) {
10093
- execFileSync("sudo", ["dpkg", "-i", downloadPath]);
10350
+ execFileSync2("sudo", ["dpkg", "-i", downloadPath]);
10094
10351
  } else if (downloadPath.endsWith(".rpm")) {
10095
- execFileSync("sudo", ["rpm", "-U", downloadPath]);
10352
+ execFileSync2("sudo", ["rpm", "-U", downloadPath]);
10096
10353
  } else if (downloadPath.endsWith(".tar.gz")) {
10097
- const appDir = dirname(this.config.appPath);
10098
- execFileSync("tar", ["-xzf", downloadPath, "-C", appDir]);
10354
+ const appDir = dirname2(this.config.appPath);
10355
+ execFileSync2("tar", ["-xzf", downloadPath, "-C", appDir]);
10099
10356
  }
10100
10357
  }
10101
- restartApp() {
10102
- const platform = this.getPlatform();
10358
+ async restartApp() {
10103
10359
  const appPath = this.config.appPath;
10104
- switch (platform) {
10360
+ if (this.config.relaunch) {
10361
+ await this.config.relaunch(appPath);
10362
+ return;
10363
+ }
10364
+ switch (this.getPlatform()) {
10105
10365
  case "darwin":
10106
10366
  spawn3("open", ["-n", appPath], { detached: true, stdio: "ignore" });
10107
10367
  break;
10108
10368
  case "win32":
10109
- spawn3(appPath, [], { detached: true, stdio: "ignore" });
10110
- break;
10111
10369
  case "linux":
10112
10370
  spawn3(appPath, [], { detached: true, stdio: "ignore" });
10113
10371
  break;
@@ -10183,18 +10441,32 @@ class AutoUpdater extends EventEmitter3 {
10183
10441
  }
10184
10442
 
10185
10443
  class DeltaGenerator {
10444
+ static supported = null;
10445
+ static isSupported() {
10446
+ if (DeltaGenerator.supported !== null)
10447
+ return DeltaGenerator.supported;
10448
+ DeltaGenerator.supported = ["bspatch", "xdelta3"].some((tool) => {
10449
+ try {
10450
+ execFileSync2("command", ["-v", tool], { stdio: "ignore", shell: true });
10451
+ return true;
10452
+ } catch {
10453
+ return false;
10454
+ }
10455
+ });
10456
+ return DeltaGenerator.supported;
10457
+ }
10186
10458
  static async generate(oldPath, newPath, outputPath) {
10187
10459
  try {
10188
- execFileSync("bsdiff", [oldPath, newPath, outputPath]);
10189
- const stats = statSync(outputPath);
10460
+ execFileSync2("bsdiff", [oldPath, newPath, outputPath]);
10461
+ const stats = statSync2(outputPath);
10190
10462
  const hash = await DeltaGenerator.computeHash(outputPath);
10191
10463
  return {
10192
10464
  size: stats.size,
10193
10465
  sha256: hash
10194
10466
  };
10195
10467
  } catch {
10196
- execFileSync("xdelta3", ["-e", "-s", oldPath, newPath, outputPath]);
10197
- const stats = statSync(outputPath);
10468
+ execFileSync2("xdelta3", ["-e", "-s", oldPath, newPath, outputPath]);
10469
+ const stats = statSync2(outputPath);
10198
10470
  const hash = await DeltaGenerator.computeHash(outputPath);
10199
10471
  return {
10200
10472
  size: stats.size,
@@ -10204,9 +10476,9 @@ class DeltaGenerator {
10204
10476
  }
10205
10477
  static async apply(basePath, deltaPath, outputPath) {
10206
10478
  try {
10207
- execFileSync("bspatch", [basePath, outputPath, deltaPath]);
10479
+ execFileSync2("bspatch", [basePath, outputPath, deltaPath]);
10208
10480
  } catch {
10209
- execFileSync("xdelta3", ["-d", "-s", basePath, deltaPath, outputPath]);
10481
+ execFileSync2("xdelta3", ["-d", "-s", basePath, deltaPath, outputPath]);
10210
10482
  }
10211
10483
  }
10212
10484
  static async computeHash(filePath) {
@@ -10334,11 +10606,11 @@ Examples:
10334
10606
  var updater_default = AutoUpdater;
10335
10607
 
10336
10608
  // src/index.ts
10337
- var execFileAsync = promisify(execFile);
10609
+ var execFileAsync2 = promisify2(execFile2);
10338
10610
  var versionMismatchWarned = false;
10339
10611
  function getSdkVersion() {
10340
10612
  try {
10341
- const pkgPath = join3(import.meta.dir, "..", "package.json");
10613
+ const pkgPath = join4(import.meta.dir, "..", "package.json");
10342
10614
  return JSON.parse(readFileSync3(pkgPath, "utf-8")).version ?? "unknown";
10343
10615
  } catch {
10344
10616
  return "unknown";
@@ -10348,7 +10620,7 @@ async function probeBinaryVersion(craftPath) {
10348
10620
  if (versionMismatchWarned)
10349
10621
  return;
10350
10622
  try {
10351
- const { stdout } = await execFileAsync(craftPath, ["--version"], { timeout: 2000 });
10623
+ const { stdout } = await execFileAsync2(craftPath, ["--version"], { timeout: 2000 });
10352
10624
  const nativeVersion = parseCraftVersionOutput(stdout);
10353
10625
  const sdkVersion = getSdkVersion();
10354
10626
  if (sdkVersion !== "unknown" && nativeVersion && nativeVersion !== sdkVersion) {
@@ -10565,8 +10837,8 @@ class CraftApp {
10565
10837
  if (safeForArgv) {
10566
10838
  args.push("--sidebar-config", json);
10567
10839
  } else {
10568
- const dir = mkdtempSync2(join3(tmpdir2(), "craft-sidebar-"));
10569
- const path = join3(dir, "sidebar-config.json");
10840
+ const dir = mkdtempSync3(join4(tmpdir4(), "craft-sidebar-"));
10841
+ const path = join4(dir, "sidebar-config.json");
10570
10842
  writeFileSync3(path, json, "utf-8");
10571
10843
  args.push("--sidebar-config-file", path);
10572
10844
  }
@@ -10605,6 +10877,7 @@ export {
10605
10877
  watch,
10606
10878
  vueOptimizations,
10607
10879
  verifyPassword,
10880
+ verifyBundleTrust,
10608
10881
  variants,
10609
10882
  uuid,
10610
10883
  exports_updater as updater,
@@ -10623,6 +10896,7 @@ export {
10623
10896
  tahoeStyles,
10624
10897
  tahoeStyle,
10625
10898
  tahoeDemoData,
10899
+ swapBundle,
10626
10900
  svelteOptimizations,
10627
10901
  submenu,
10628
10902
  styles,
@@ -10670,6 +10944,7 @@ export {
10670
10944
  registerShortcut,
10671
10945
  readText,
10672
10946
  readHTML,
10947
+ readBundleIdentity,
10673
10948
  readBinaryFile,
10674
10949
  reactOptimizations,
10675
10950
  randomString,
@@ -10737,9 +11012,13 @@ export {
10737
11012
  foregroundService,
10738
11013
  focusFilters,
10739
11014
  focus,
11015
+ extractBundleFromZip,
11016
+ extractBundleFromDmg,
11017
+ extractBundle,
10740
11018
  exit,
10741
11019
  exec,
10742
11020
  env,
11021
+ dittoBundle,
10743
11022
  dialog,
10744
11023
  device,
10745
11024
  desktopWidgets,
@@ -10770,8 +11049,10 @@ export {
10770
11049
  commonOptimizations,
10771
11050
  cx as clsx,
10772
11051
  clipboard,
11052
+ clearQuarantine,
10773
11053
  checkbox,
10774
11054
  carplay,
11055
+ canReplaceBundle,
10775
11056
  canQuickLook,
10776
11057
  camera,
10777
11058
  buildMenu,