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.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";
@@ -2302,6 +2302,9 @@ class Window {
2302
2302
  async setVibrancy(vibrancy) {
2303
2303
  await this._call("setVibrancy", { vibrancy });
2304
2304
  }
2305
+ async setAppearance(appearance) {
2306
+ await this._call("setAppearance", { appearance });
2307
+ }
2305
2308
  get windowControls() {
2306
2309
  return globalThis.craft?.windowControls;
2307
2310
  }
@@ -2401,6 +2404,7 @@ class WindowManager {
2401
2404
  setOpacity = (opacity) => this.current.setOpacity(opacity);
2402
2405
  setBackgroundColor = (color) => this.current.setBackgroundColor(color);
2403
2406
  setVibrancy = (vibrancy) => this.current.setVibrancy(vibrancy);
2407
+ setAppearance = (appearance) => this.current.setAppearance(appearance);
2404
2408
  setResizable = (resizable) => this.current.setResizable(resizable);
2405
2409
  startDrag = () => this.current.startDrag();
2406
2410
  getState = () => this.current.getState();
@@ -9749,24 +9753,236 @@ var commonOptimizations = {
9749
9753
  // src/updater/index.ts
9750
9754
  var exports_updater = {};
9751
9755
  __export(exports_updater, {
9756
+ verifyBundleTrust: () => verifyBundleTrust,
9752
9757
  updaterCommand: () => updaterCommand,
9758
+ swapBundle: () => swapBundle,
9759
+ readBundleIdentity: () => readBundleIdentity,
9760
+ isMacOS: () => isMacOS,
9753
9761
  generateUpdateManifest: () => generateUpdateManifest,
9762
+ extractBundleFromZip: () => extractBundleFromZip,
9763
+ extractBundleFromDmg: () => extractBundleFromDmg,
9764
+ extractBundle: () => extractBundle,
9765
+ dittoBundle: () => dittoBundle,
9754
9766
  default: () => updater_default,
9767
+ clearQuarantine: () => clearQuarantine,
9768
+ canReplaceBundle: () => canReplaceBundle,
9755
9769
  DeltaGenerator: () => DeltaGenerator,
9756
9770
  AutoUpdater: () => AutoUpdater
9757
9771
  });
9758
- import { createReadStream, createWriteStream, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, statSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
9759
- import { join as join2, basename as basename2, dirname } from "path";
9760
- import { execFileSync, spawn as spawn3 } from "child_process";
9772
+ 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";
9773
+ import { join as join3, basename as basename3, dirname as dirname2 } from "path";
9774
+ import { tmpdir as tmpdir3 } from "os";
9775
+ import { execFileSync as execFileSync2, spawn as spawn3 } from "child_process";
9761
9776
  import { createHash as createHash2 } from "crypto";
9762
9777
  import { EventEmitter as EventEmitter3 } from "events";
9763
- function findFirstByName(root, namePattern, kind) {
9764
- const out = execFileSync("find", [root, "-name", namePattern, "-type", kind, "-print0"]);
9765
- const nullByte = String.fromCharCode(0);
9766
- const parts = out.toString("utf-8").split(nullByte).filter((s) => s.length > 0);
9767
- return parts[0] ?? "";
9778
+
9779
+ // src/updater/macos-bundle.ts
9780
+ import { execFile, execFileSync } from "child_process";
9781
+ import { accessSync, constants, existsSync as existsSync3, mkdtempSync as mkdtempSync2, renameSync, rmSync as rmSync2, statSync } from "fs";
9782
+ import { tmpdir as tmpdir2 } from "os";
9783
+ import { basename as basename2, dirname, join as join2 } from "path";
9784
+ import { promisify } from "util";
9785
+ var execFileAsync = promisify(execFile);
9786
+ function isMacOS() {
9787
+ return process.platform === "darwin";
9788
+ }
9789
+ async function readBundleIdentity(appPath) {
9790
+ const empty = { identifier: null, teamId: null, authority: null };
9791
+ if (!isMacOS())
9792
+ return empty;
9793
+ let report;
9794
+ try {
9795
+ const { stderr, stdout } = await execFileAsync("codesign", ["-dv", "--verbose=4", appPath]);
9796
+ report = `${stderr}
9797
+ ${stdout}`;
9798
+ } catch (error) {
9799
+ const e = error;
9800
+ report = `${e.stderr ?? ""}
9801
+ ${e.stdout ?? ""}`;
9802
+ if (!report.trim())
9803
+ return empty;
9804
+ }
9805
+ const field = (name) => {
9806
+ const match = report.match(new RegExp(`^${name}=(.+)$`, "m"));
9807
+ if (!match)
9808
+ return null;
9809
+ const value = match[1].trim();
9810
+ return value === "not set" || value.length === 0 ? null : value;
9811
+ };
9812
+ return {
9813
+ identifier: field("Identifier"),
9814
+ teamId: field("TeamIdentifier"),
9815
+ authority: field("Authority")
9816
+ };
9817
+ }
9818
+ async function verifyBundleTrust(appPath, policy = {}) {
9819
+ const identity = await readBundleIdentity(appPath);
9820
+ const requireNotarized = policy.requireNotarized ?? true;
9821
+ if (!existsSync3(appPath)) {
9822
+ return { ok: false, identity, gatekeeperSource: null, reason: "unreadable", detail: `No such bundle: ${appPath}` };
9823
+ }
9824
+ try {
9825
+ await execFileAsync("codesign", ["--verify", "--deep", "--strict", "--verbose=2", appPath]);
9826
+ } catch (error) {
9827
+ return {
9828
+ ok: false,
9829
+ identity,
9830
+ gatekeeperSource: null,
9831
+ reason: "codesign-invalid",
9832
+ detail: describe(error)
9833
+ };
9834
+ }
9835
+ let gatekeeperSource = null;
9836
+ if (requireNotarized) {
9837
+ try {
9838
+ const { stderr, stdout } = await execFileAsync("spctl", ["-a", "-t", "exec", "-vv", appPath]);
9839
+ const report = `${stderr}
9840
+ ${stdout}`;
9841
+ gatekeeperSource = report.match(/^source=(.+)$/m)?.[1]?.trim() ?? null;
9842
+ } catch (error) {
9843
+ return {
9844
+ ok: false,
9845
+ identity,
9846
+ gatekeeperSource: null,
9847
+ reason: "gatekeeper-rejected",
9848
+ detail: describe(error)
9849
+ };
9850
+ }
9851
+ }
9852
+ if (policy.teamId && identity.teamId !== policy.teamId) {
9853
+ return {
9854
+ ok: false,
9855
+ identity,
9856
+ gatekeeperSource,
9857
+ reason: "team-mismatch",
9858
+ detail: `Expected Team ID ${policy.teamId}, bundle is signed by ${identity.teamId ?? "nobody"}`
9859
+ };
9860
+ }
9861
+ return { ok: true, identity, gatekeeperSource };
9862
+ }
9863
+ async function clearQuarantine(appPath) {
9864
+ if (!isMacOS())
9865
+ return;
9866
+ try {
9867
+ await execFileAsync("xattr", ["-d", "-r", "com.apple.quarantine", appPath]);
9868
+ } catch {}
9869
+ }
9870
+ async function dittoBundle(source, destination) {
9871
+ await execFileAsync("ditto", [source, destination]);
9872
+ }
9873
+ function findTopLevelApp(root) {
9874
+ const out = execFileSync("find", [root, "-maxdepth", "2", "-name", "*.app", "-type", "d", "-print0"]);
9875
+ const parts = out.toString("utf-8").split("\x00").filter((s) => s.length > 0);
9876
+ parts.sort((a, b) => a.split("/").length - b.split("/").length);
9877
+ return parts[0] ?? null;
9878
+ }
9879
+ async function extractBundleFromDmg(dmgPath, stagingDir) {
9880
+ const staging = stagingDir ?? mkdtempSync2(join2(tmpdir2(), "craft-update-"));
9881
+ const mountPoint = mkdtempSync2(join2(tmpdir2(), "craft-mount-"));
9882
+ let mounted = false;
9883
+ try {
9884
+ await execFileAsync("hdiutil", ["attach", dmgPath, "-nobrowse", "-readonly", "-mountpoint", mountPoint]);
9885
+ mounted = true;
9886
+ const source = findTopLevelApp(mountPoint);
9887
+ if (!source)
9888
+ throw new Error(`No .app found in disk image: ${dmgPath}`);
9889
+ const appPath = join2(staging, basename2(source));
9890
+ await dittoBundle(source, appPath);
9891
+ return { appPath, stagingDir: staging };
9892
+ } finally {
9893
+ if (mounted) {
9894
+ try {
9895
+ await execFileAsync("hdiutil", ["detach", mountPoint, "-force"]);
9896
+ } catch {}
9897
+ }
9898
+ try {
9899
+ rmSync2(mountPoint, { recursive: true, force: true });
9900
+ } catch {}
9901
+ }
9902
+ }
9903
+ async function extractBundleFromZip(zipPath, stagingDir) {
9904
+ const staging = stagingDir ?? mkdtempSync2(join2(tmpdir2(), "craft-update-"));
9905
+ const unpacked = join2(staging, "payload");
9906
+ await execFileAsync("ditto", ["-x", "-k", zipPath, unpacked]);
9907
+ const source = findTopLevelApp(unpacked);
9908
+ if (!source)
9909
+ throw new Error(`No .app found in archive: ${zipPath}`);
9910
+ return { appPath: source, stagingDir: staging };
9911
+ }
9912
+ async function extractBundle(archivePath, stagingDir) {
9913
+ if (archivePath.endsWith(".dmg"))
9914
+ return extractBundleFromDmg(archivePath, stagingDir);
9915
+ if (archivePath.endsWith(".zip"))
9916
+ return extractBundleFromZip(archivePath, stagingDir);
9917
+ throw new Error(`Cannot extract an app bundle from ${archivePath}`);
9918
+ }
9919
+ async function swapBundle(stagedPath, installedPath) {
9920
+ if (!existsSync3(stagedPath))
9921
+ throw new Error(`Staged bundle is missing: ${stagedPath}`);
9922
+ const parent = dirname(installedPath);
9923
+ const stamp = `${Date.now()}.${process.pid}`;
9924
+ const incoming = join2(parent, `.${basename2(installedPath)}.${stamp}.incoming`);
9925
+ const retired = join2(parent, `.${basename2(installedPath)}.${stamp}.retired`);
9926
+ try {
9927
+ renameSync(stagedPath, incoming);
9928
+ } catch {
9929
+ await dittoBundle(stagedPath, incoming);
9930
+ }
9931
+ const hadPrevious = existsSync3(installedPath);
9932
+ try {
9933
+ if (hadPrevious)
9934
+ renameSync(installedPath, retired);
9935
+ try {
9936
+ renameSync(incoming, installedPath);
9937
+ } catch (error) {
9938
+ if (hadPrevious) {
9939
+ try {
9940
+ renameSync(retired, installedPath);
9941
+ } catch {}
9942
+ }
9943
+ throw error;
9944
+ }
9945
+ } catch (error) {
9946
+ try {
9947
+ rmSync2(incoming, { recursive: true, force: true });
9948
+ } catch {}
9949
+ throw error;
9950
+ }
9951
+ if (hadPrevious) {
9952
+ try {
9953
+ rmSync2(retired, { recursive: true, force: true });
9954
+ return { previousPath: null };
9955
+ } catch {
9956
+ return { previousPath: retired };
9957
+ }
9958
+ }
9959
+ return { previousPath: null };
9960
+ }
9961
+ function canReplaceBundle(installedPath) {
9962
+ try {
9963
+ const parent = dirname(installedPath);
9964
+ accessSync(parent, constants.W_OK);
9965
+ if (existsSync3(installedPath))
9966
+ statSync(installedPath);
9967
+ return true;
9968
+ } catch {
9969
+ return false;
9970
+ }
9971
+ }
9972
+ function describe(error) {
9973
+ if (error && typeof error === "object") {
9974
+ const e = error;
9975
+ const stderr = e.stderr?.trim();
9976
+ if (stderr)
9977
+ return stderr;
9978
+ if (e.message)
9979
+ return e.message;
9980
+ }
9981
+ return String(error);
9768
9982
  }
9769
9983
 
9984
+ // src/updater/index.ts
9985
+ var PROGRESS_INTERVAL_MS = 100;
9770
9986
  class AutoUpdater extends EventEmitter3 {
9771
9987
  config;
9772
9988
  updateInfo = null;
@@ -9775,12 +9991,22 @@ class AutoUpdater extends EventEmitter3 {
9775
9991
  cachedEtag = null;
9776
9992
  cachedLastModified = null;
9777
9993
  cachedManifest = null;
9994
+ lastError = null;
9778
9995
  emitError(error) {
9996
+ this.lastError = error instanceof Error ? error : new Error(String(error));
9779
9997
  if (this.listenerCount("error") > 0)
9780
9998
  this.emit("error", error);
9781
9999
  else
9782
10000
  console.error("[Updater]", error);
9783
10001
  }
10002
+ downloadDir() {
10003
+ if (this.config.downloadDir)
10004
+ return this.config.downloadDir;
10005
+ return join3(tmpdir3(), "craft-updates", basename3(this.config.appPath) || "app");
10006
+ }
10007
+ getLastError() {
10008
+ return this.lastError;
10009
+ }
9784
10010
  constructor(config) {
9785
10011
  super();
9786
10012
  const url = config.updateUrl;
@@ -9814,6 +10040,7 @@ class AutoUpdater extends EventEmitter3 {
9814
10040
  }
9815
10041
  async checkForUpdates() {
9816
10042
  this.emit("checking-for-update");
10043
+ this.lastError = null;
9817
10044
  try {
9818
10045
  const platform = this.getPlatform();
9819
10046
  const url = `${this.config.updateUrl}?v=${this.config.currentVersion}&channel=${this.config.channel}&platform=${platform}`;
@@ -9862,20 +10089,21 @@ class AutoUpdater extends EventEmitter3 {
9862
10089
  if (!this.updateInfo) {
9863
10090
  throw new Error("No update available");
9864
10091
  }
10092
+ this.lastError = null;
9865
10093
  const platform = this.getPlatform();
9866
10094
  const platformUpdate = this.updateInfo.platforms[platform];
9867
10095
  if (!platformUpdate) {
9868
10096
  throw new Error(`No update available for platform: ${platform}`);
9869
10097
  }
9870
- const deltaUpdate = platformUpdate.delta?.find((d) => d.fromVersion === this.config.currentVersion);
10098
+ const deltaUpdate = DeltaGenerator.isSupported() ? platformUpdate.delta?.find((d) => d.fromVersion === this.config.currentVersion) : undefined;
9871
10099
  const _updateSource = deltaUpdate || platformUpdate;
9872
10100
  const url = deltaUpdate?.url || platformUpdate.url;
9873
10101
  const expectedSize = deltaUpdate?.size || platformUpdate.size;
9874
10102
  const expectedHash = deltaUpdate?.sha256 || platformUpdate.sha256;
9875
- const downloadDir = join2(this.config.appPath, "..", ".craft-updates");
10103
+ const downloadDir = this.downloadDir();
9876
10104
  mkdirSync2(downloadDir, { recursive: true });
9877
- const fileName = basename2(url);
9878
- this.downloadPath = join2(downloadDir, fileName);
10105
+ const fileName = basename3(url);
10106
+ this.downloadPath = join3(downloadDir, fileName);
9879
10107
  try {
9880
10108
  const response = await fetch(url);
9881
10109
  if (!response.ok) {
@@ -9890,39 +10118,59 @@ class AutoUpdater extends EventEmitter3 {
9890
10118
  if (!reader) {
9891
10119
  throw new Error("Failed to get response reader");
9892
10120
  }
10121
+ const digest = createHash2("sha256");
10122
+ let lastProgressAt = 0;
9893
10123
  while (true) {
9894
10124
  const { done, value } = await reader.read();
9895
10125
  if (done)
9896
10126
  break;
9897
- fileStream.write(value);
10127
+ digest.update(value);
10128
+ if (!fileStream.write(value)) {
10129
+ await new Promise((resolve, reject) => {
10130
+ const onDrain = () => {
10131
+ fileStream.off("error", onError);
10132
+ resolve();
10133
+ };
10134
+ const onError = (error) => {
10135
+ fileStream.off("drain", onDrain);
10136
+ reject(error);
10137
+ };
10138
+ fileStream.once("drain", onDrain);
10139
+ fileStream.once("error", onError);
10140
+ });
10141
+ }
9898
10142
  downloaded += value.length;
9899
- const elapsed = (Date.now() - startTime) / 1000;
9900
- const speed = downloaded / elapsed;
9901
- const progress = {
9902
- phase: "downloading",
9903
- percent: Math.round(downloaded / total * 100),
9904
- bytesDownloaded: downloaded,
9905
- bytesTotal: total,
9906
- speed
9907
- };
9908
- this.emit("download-progress", progress);
10143
+ const now = Date.now();
10144
+ if (now - lastProgressAt >= PROGRESS_INTERVAL_MS || downloaded === total) {
10145
+ lastProgressAt = now;
10146
+ const elapsed = (now - startTime) / 1000;
10147
+ const speed = elapsed > 0 ? downloaded / elapsed : 0;
10148
+ const progress = {
10149
+ phase: "downloading",
10150
+ percent: total > 0 ? Math.round(downloaded / total * 100) : 0,
10151
+ bytesDownloaded: downloaded,
10152
+ bytesTotal: total,
10153
+ speed
10154
+ };
10155
+ this.emit("download-progress", progress);
10156
+ }
9909
10157
  }
9910
10158
  fileStream.end();
9911
10159
  await new Promise((resolve, reject) => {
9912
10160
  fileStream.once("finish", resolve);
9913
10161
  fileStream.once("error", reject);
9914
10162
  });
9915
- const hash = await this.computeFileHash(this.downloadPath);
10163
+ const hash = digest.digest("hex");
9916
10164
  if (hash !== expectedHash) {
9917
10165
  unlinkSync(this.downloadPath);
9918
10166
  throw new Error("Download verification failed: hash mismatch");
9919
10167
  }
9920
10168
  if (deltaUpdate) {
9921
- if (!statSync(this.config.appPath).isFile()) {
10169
+ if (!statSync2(this.config.appPath).isFile()) {
9922
10170
  throw new Error("Delta updates require appPath to point to the installed bundle file");
9923
10171
  }
9924
10172
  const patchPath = this.downloadPath;
9925
- const reconstructedPath = join2(downloadDir, `reconstructed-${basename2(platformUpdate.url)}`);
10173
+ const reconstructedPath = join3(downloadDir, `reconstructed-${basename3(platformUpdate.url)}`);
9926
10174
  await DeltaGenerator.apply(this.config.appPath, patchPath, reconstructedPath);
9927
10175
  const reconstructedHash = await this.computeFileHash(reconstructedPath);
9928
10176
  if (reconstructedHash !== platformUpdate.sha256) {
@@ -9960,7 +10208,7 @@ class AutoUpdater extends EventEmitter3 {
9960
10208
  }
9961
10209
  }
9962
10210
  async installUpdate(restartAfter = true) {
9963
- if (!this.downloadPath || !existsSync3(this.downloadPath)) {
10211
+ if (!this.downloadPath || !existsSync4(this.downloadPath)) {
9964
10212
  throw new Error("No update downloaded");
9965
10213
  }
9966
10214
  if (this.updateInfo) {
@@ -10004,14 +10252,17 @@ class AutoUpdater extends EventEmitter3 {
10004
10252
  await this.installLinuxUpdate();
10005
10253
  break;
10006
10254
  }
10007
- if (this.downloadPath && existsSync3(this.downloadPath)) {
10255
+ if (this.downloadPath && existsSync4(this.downloadPath)) {
10008
10256
  unlinkSync(this.downloadPath);
10009
10257
  }
10258
+ try {
10259
+ rmdirSync(this.downloadDir());
10260
+ } catch {}
10010
10261
  progress.phase = "done";
10011
10262
  progress.percent = 100;
10012
10263
  this.emit("download-progress", progress);
10013
10264
  if (restartAfter) {
10014
- this.restartApp();
10265
+ await this.restartApp();
10015
10266
  }
10016
10267
  } catch (error) {
10017
10268
  progress.phase = "error";
@@ -10022,38 +10273,45 @@ class AutoUpdater extends EventEmitter3 {
10022
10273
  async installMacOSUpdate() {
10023
10274
  const downloadPath = this.downloadPath;
10024
10275
  const appPath = this.config.appPath;
10025
- if (downloadPath.endsWith(".zip")) {
10026
- const tempDir2 = join2(dirname(downloadPath), "extracted");
10027
- mkdirSync2(tempDir2, { recursive: true });
10028
- execFileSync("unzip", ["-o", downloadPath, "-d", tempDir2]);
10029
- const extractedApp = findFirstByName(tempDir2, "*.app", "d");
10030
- if (extractedApp) {
10031
- const { rmSync: rmSync3, renameSync } = await import("fs");
10032
- rmSync3(appPath, { recursive: true, force: true });
10033
- renameSync(extractedApp, appPath);
10034
- }
10035
- const { rmSync: rmSync2 } = await import("fs");
10036
- rmSync2(tempDir2, { recursive: true, force: true });
10037
- } else if (downloadPath.endsWith(".dmg")) {
10038
- const mountOutput = execFileSync("hdiutil", ["attach", downloadPath, "-nobrowse"]).toString();
10039
- const mountPoint = mountOutput.match(/\/Volumes\/[^\n]+/)?.[0];
10040
- if (mountPoint) {
10041
- const dmgApp = findFirstByName(mountPoint, "*.app", "d");
10042
- if (dmgApp) {
10043
- const { rmSync: rmSync2, cpSync: cpSync2 } = await import("fs");
10044
- rmSync2(appPath, { recursive: true, force: true });
10045
- cpSync2(dmgApp, appPath, { recursive: true });
10046
- }
10047
- execFileSync("hdiutil", ["detach", mountPoint]);
10048
- }
10049
- } else if (downloadPath.endsWith(".pkg")) {
10276
+ if (downloadPath.endsWith(".pkg")) {
10277
+ await this.installMacOSPackage(downloadPath);
10278
+ return;
10279
+ }
10280
+ if (!canReplaceBundle(appPath)) {
10281
+ 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.");
10282
+ }
10283
+ const staged = await extractBundle(downloadPath);
10284
+ try {
10285
+ const trust = await this.assertBundleTrusted(staged.appPath);
10286
+ await clearQuarantine(staged.appPath);
10287
+ const { previousPath } = await swapBundle(staged.appPath, appPath);
10288
+ this.emit("update-installed", {
10289
+ path: appPath,
10290
+ version: this.updateInfo?.version,
10291
+ identity: trust.identity,
10292
+ leftoverPath: previousPath
10293
+ });
10294
+ } finally {
10050
10295
  try {
10051
- execFileSync("pkgutil", ["--check-signature", downloadPath], { stdio: "pipe" });
10052
- } catch (e) {
10053
- 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}`);
10054
- }
10055
- execFileSync("sudo", ["installer", "-pkg", downloadPath, "-target", "/"]);
10296
+ rmSync3(staged.stagingDir, { recursive: true, force: true });
10297
+ } catch {}
10298
+ }
10299
+ }
10300
+ async assertBundleTrusted(bundlePath) {
10301
+ const policy = this.config.macos ?? {};
10302
+ const trust = await verifyBundleTrust(bundlePath, policy);
10303
+ if (!trust.ok) {
10304
+ 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.");
10056
10305
  }
10306
+ return trust;
10307
+ }
10308
+ async installMacOSPackage(downloadPath) {
10309
+ try {
10310
+ execFileSync2("pkgutil", ["--check-signature", downloadPath], { stdio: "pipe" });
10311
+ } catch (e) {
10312
+ 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}`);
10313
+ }
10314
+ execFileSync2("sudo", ["installer", "-pkg", downloadPath, "-target", "/"]);
10057
10315
  }
10058
10316
  async installWindowsUpdate() {
10059
10317
  const downloadPath = this.downloadPath;
@@ -10068,8 +10326,8 @@ class AutoUpdater extends EventEmitter3 {
10068
10326
  stdio: "ignore"
10069
10327
  });
10070
10328
  } else if (downloadPath.endsWith(".zip")) {
10071
- const appDir = dirname(this.config.appPath);
10072
- execFileSync("powershell", [
10329
+ const appDir = dirname2(this.config.appPath);
10330
+ execFileSync2("powershell", [
10073
10331
  "-NoProfile",
10074
10332
  "-NonInteractive",
10075
10333
  "-Command",
@@ -10082,28 +10340,29 @@ class AutoUpdater extends EventEmitter3 {
10082
10340
  async installLinuxUpdate() {
10083
10341
  const downloadPath = this.downloadPath;
10084
10342
  if (downloadPath.endsWith(".AppImage")) {
10085
- execFileSync("chmod", ["+x", downloadPath]);
10086
- const { renameSync } = await import("fs");
10087
- renameSync(downloadPath, this.config.appPath);
10343
+ execFileSync2("chmod", ["+x", downloadPath]);
10344
+ const { renameSync: renameSync2 } = await import("fs");
10345
+ renameSync2(downloadPath, this.config.appPath);
10088
10346
  } else if (downloadPath.endsWith(".deb")) {
10089
- execFileSync("sudo", ["dpkg", "-i", downloadPath]);
10347
+ execFileSync2("sudo", ["dpkg", "-i", downloadPath]);
10090
10348
  } else if (downloadPath.endsWith(".rpm")) {
10091
- execFileSync("sudo", ["rpm", "-U", downloadPath]);
10349
+ execFileSync2("sudo", ["rpm", "-U", downloadPath]);
10092
10350
  } else if (downloadPath.endsWith(".tar.gz")) {
10093
- const appDir = dirname(this.config.appPath);
10094
- execFileSync("tar", ["-xzf", downloadPath, "-C", appDir]);
10351
+ const appDir = dirname2(this.config.appPath);
10352
+ execFileSync2("tar", ["-xzf", downloadPath, "-C", appDir]);
10095
10353
  }
10096
10354
  }
10097
- restartApp() {
10098
- const platform = this.getPlatform();
10355
+ async restartApp() {
10099
10356
  const appPath = this.config.appPath;
10100
- switch (platform) {
10357
+ if (this.config.relaunch) {
10358
+ await this.config.relaunch(appPath);
10359
+ return;
10360
+ }
10361
+ switch (this.getPlatform()) {
10101
10362
  case "darwin":
10102
10363
  spawn3("open", ["-n", appPath], { detached: true, stdio: "ignore" });
10103
10364
  break;
10104
10365
  case "win32":
10105
- spawn3(appPath, [], { detached: true, stdio: "ignore" });
10106
- break;
10107
10366
  case "linux":
10108
10367
  spawn3(appPath, [], { detached: true, stdio: "ignore" });
10109
10368
  break;
@@ -10179,18 +10438,32 @@ class AutoUpdater extends EventEmitter3 {
10179
10438
  }
10180
10439
 
10181
10440
  class DeltaGenerator {
10441
+ static supported = null;
10442
+ static isSupported() {
10443
+ if (DeltaGenerator.supported !== null)
10444
+ return DeltaGenerator.supported;
10445
+ DeltaGenerator.supported = ["bspatch", "xdelta3"].some((tool) => {
10446
+ try {
10447
+ execFileSync2("command", ["-v", tool], { stdio: "ignore", shell: true });
10448
+ return true;
10449
+ } catch {
10450
+ return false;
10451
+ }
10452
+ });
10453
+ return DeltaGenerator.supported;
10454
+ }
10182
10455
  static async generate(oldPath, newPath, outputPath) {
10183
10456
  try {
10184
- execFileSync("bsdiff", [oldPath, newPath, outputPath]);
10185
- const stats = statSync(outputPath);
10457
+ execFileSync2("bsdiff", [oldPath, newPath, outputPath]);
10458
+ const stats = statSync2(outputPath);
10186
10459
  const hash = await DeltaGenerator.computeHash(outputPath);
10187
10460
  return {
10188
10461
  size: stats.size,
10189
10462
  sha256: hash
10190
10463
  };
10191
10464
  } catch {
10192
- execFileSync("xdelta3", ["-e", "-s", oldPath, newPath, outputPath]);
10193
- const stats = statSync(outputPath);
10465
+ execFileSync2("xdelta3", ["-e", "-s", oldPath, newPath, outputPath]);
10466
+ const stats = statSync2(outputPath);
10194
10467
  const hash = await DeltaGenerator.computeHash(outputPath);
10195
10468
  return {
10196
10469
  size: stats.size,
@@ -10200,9 +10473,9 @@ class DeltaGenerator {
10200
10473
  }
10201
10474
  static async apply(basePath, deltaPath, outputPath) {
10202
10475
  try {
10203
- execFileSync("bspatch", [basePath, outputPath, deltaPath]);
10476
+ execFileSync2("bspatch", [basePath, outputPath, deltaPath]);
10204
10477
  } catch {
10205
- execFileSync("xdelta3", ["-d", "-s", basePath, deltaPath, outputPath]);
10478
+ execFileSync2("xdelta3", ["-d", "-s", basePath, deltaPath, outputPath]);
10206
10479
  }
10207
10480
  }
10208
10481
  static async computeHash(filePath) {
@@ -10330,11 +10603,11 @@ Examples:
10330
10603
  var updater_default = AutoUpdater;
10331
10604
 
10332
10605
  // src/index.ts
10333
- var execFileAsync = promisify(execFile);
10606
+ var execFileAsync2 = promisify2(execFile2);
10334
10607
  var versionMismatchWarned = false;
10335
10608
  function getSdkVersion() {
10336
10609
  try {
10337
- const pkgPath = join3(import.meta.dir, "..", "package.json");
10610
+ const pkgPath = join4(import.meta.dir, "..", "package.json");
10338
10611
  return JSON.parse(readFileSync3(pkgPath, "utf-8")).version ?? "unknown";
10339
10612
  } catch {
10340
10613
  return "unknown";
@@ -10344,7 +10617,7 @@ async function probeBinaryVersion(craftPath) {
10344
10617
  if (versionMismatchWarned)
10345
10618
  return;
10346
10619
  try {
10347
- const { stdout } = await execFileAsync(craftPath, ["--version"], { timeout: 2000 });
10620
+ const { stdout } = await execFileAsync2(craftPath, ["--version"], { timeout: 2000 });
10348
10621
  const nativeVersion = parseCraftVersionOutput(stdout);
10349
10622
  const sdkVersion = getSdkVersion();
10350
10623
  if (sdkVersion !== "unknown" && nativeVersion && nativeVersion !== sdkVersion) {
@@ -10519,7 +10792,9 @@ class CraftApp {
10519
10792
  args.push("--keep-running");
10520
10793
  else if (window3?.keepRunning === false)
10521
10794
  args.push("--quit-on-close");
10522
- if (window3?.webSidebarMaterial) {
10795
+ if (window3?.webWindowMaterial) {
10796
+ args.push("--web-window-material");
10797
+ } else if (window3?.webSidebarMaterial) {
10523
10798
  args.push("--web-sidebar-material");
10524
10799
  if (window3?.webSidebarWidth)
10525
10800
  args.push("--web-sidebar-width", String(window3.webSidebarWidth));
@@ -10559,8 +10834,8 @@ class CraftApp {
10559
10834
  if (safeForArgv) {
10560
10835
  args.push("--sidebar-config", json);
10561
10836
  } else {
10562
- const dir = mkdtempSync2(join3(tmpdir2(), "craft-sidebar-"));
10563
- const path = join3(dir, "sidebar-config.json");
10837
+ const dir = mkdtempSync3(join4(tmpdir4(), "craft-sidebar-"));
10838
+ const path = join4(dir, "sidebar-config.json");
10564
10839
  writeFileSync3(path, json, "utf-8");
10565
10840
  args.push("--sidebar-config-file", path);
10566
10841
  }
@@ -10599,6 +10874,7 @@ export {
10599
10874
  watch,
10600
10875
  vueOptimizations,
10601
10876
  verifyPassword,
10877
+ verifyBundleTrust,
10602
10878
  variants,
10603
10879
  uuid,
10604
10880
  exports_updater as updater,
@@ -10617,6 +10893,7 @@ export {
10617
10893
  tahoeStyles,
10618
10894
  tahoeStyle,
10619
10895
  tahoeDemoData,
10896
+ swapBundle,
10620
10897
  svelteOptimizations,
10621
10898
  submenu,
10622
10899
  styles,
@@ -10664,6 +10941,7 @@ export {
10664
10941
  registerShortcut,
10665
10942
  readText,
10666
10943
  readHTML,
10944
+ readBundleIdentity,
10667
10945
  readBinaryFile,
10668
10946
  reactOptimizations,
10669
10947
  randomString,
@@ -10731,9 +11009,13 @@ export {
10731
11009
  foregroundService,
10732
11010
  focusFilters,
10733
11011
  focus,
11012
+ extractBundleFromZip,
11013
+ extractBundleFromDmg,
11014
+ extractBundle,
10734
11015
  exit,
10735
11016
  exec,
10736
11017
  env,
11018
+ dittoBundle,
10737
11019
  dialog,
10738
11020
  device,
10739
11021
  desktopWidgets,
@@ -10764,8 +11046,10 @@ export {
10764
11046
  commonOptimizations,
10765
11047
  cx as clsx,
10766
11048
  clipboard,
11049
+ clearQuarantine,
10767
11050
  checkbox,
10768
11051
  carplay,
11052
+ canReplaceBundle,
10769
11053
  canQuickLook,
10770
11054
  camera,
10771
11055
  buildMenu,