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/README.md +71 -0
- package/dist/api/window.d.ts +14 -1
- package/dist/cli.js +75 -3
- package/dist/index.cjs +386 -102
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +374 -90
- package/dist/ios/src/index.d.ts +74 -0
- package/dist/ios/src/index.js +73 -1
- package/dist/ios/templates/CraftApp.swift +510 -112
- package/dist/ios/templates/project.yml.template +1 -0
- package/dist/types.d.ts +22 -0
- package/dist/updater/index.d.ts +104 -1
- package/dist/updater/macos-bundle.d.ts +153 -0
- package/package.json +2 -2
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
|
|
21
|
-
import { tmpdir as
|
|
22
|
-
import { join as
|
|
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
|
|
9759
|
-
import { join as
|
|
9760
|
-
import {
|
|
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
|
-
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
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 =
|
|
10103
|
+
const downloadDir = this.downloadDir();
|
|
9876
10104
|
mkdirSync2(downloadDir, { recursive: true });
|
|
9877
|
-
const fileName =
|
|
9878
|
-
this.downloadPath =
|
|
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
|
-
|
|
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
|
|
9900
|
-
|
|
9901
|
-
|
|
9902
|
-
|
|
9903
|
-
|
|
9904
|
-
|
|
9905
|
-
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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 || !
|
|
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 &&
|
|
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(".
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
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
|
-
|
|
10052
|
-
} catch
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
|
|
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 =
|
|
10072
|
-
|
|
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
|
-
|
|
10086
|
-
const { renameSync } = await import("fs");
|
|
10087
|
-
|
|
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
|
-
|
|
10347
|
+
execFileSync2("sudo", ["dpkg", "-i", downloadPath]);
|
|
10090
10348
|
} else if (downloadPath.endsWith(".rpm")) {
|
|
10091
|
-
|
|
10349
|
+
execFileSync2("sudo", ["rpm", "-U", downloadPath]);
|
|
10092
10350
|
} else if (downloadPath.endsWith(".tar.gz")) {
|
|
10093
|
-
const appDir =
|
|
10094
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10185
|
-
const stats =
|
|
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
|
-
|
|
10193
|
-
const stats =
|
|
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
|
-
|
|
10476
|
+
execFileSync2("bspatch", [basePath, outputPath, deltaPath]);
|
|
10204
10477
|
} catch {
|
|
10205
|
-
|
|
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
|
|
10606
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
10334
10607
|
var versionMismatchWarned = false;
|
|
10335
10608
|
function getSdkVersion() {
|
|
10336
10609
|
try {
|
|
10337
|
-
const pkgPath =
|
|
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
|
|
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?.
|
|
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 =
|
|
10563
|
-
const path =
|
|
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,
|