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/README.md +71 -0
- package/dist/api/window.d.ts +33 -0
- package/dist/cli.js +75 -3
- package/dist/index.cjs +384 -103
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +372 -91
- 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/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";
|
|
@@ -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", {
|
|
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
|
|
9763
|
-
import { join as
|
|
9764
|
-
import {
|
|
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
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
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 =
|
|
10106
|
+
const downloadDir = this.downloadDir();
|
|
9880
10107
|
mkdirSync2(downloadDir, { recursive: true });
|
|
9881
|
-
const fileName =
|
|
9882
|
-
this.downloadPath =
|
|
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
|
-
|
|
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
|
|
9904
|
-
|
|
9905
|
-
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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 || !
|
|
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 &&
|
|
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(".
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
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
|
-
|
|
10056
|
-
} catch
|
|
10057
|
-
|
|
10058
|
-
|
|
10059
|
-
|
|
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 =
|
|
10076
|
-
|
|
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
|
-
|
|
10090
|
-
const { renameSync } = await import("fs");
|
|
10091
|
-
|
|
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
|
-
|
|
10350
|
+
execFileSync2("sudo", ["dpkg", "-i", downloadPath]);
|
|
10094
10351
|
} else if (downloadPath.endsWith(".rpm")) {
|
|
10095
|
-
|
|
10352
|
+
execFileSync2("sudo", ["rpm", "-U", downloadPath]);
|
|
10096
10353
|
} else if (downloadPath.endsWith(".tar.gz")) {
|
|
10097
|
-
const appDir =
|
|
10098
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10189
|
-
const stats =
|
|
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
|
-
|
|
10197
|
-
const stats =
|
|
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
|
-
|
|
10479
|
+
execFileSync2("bspatch", [basePath, outputPath, deltaPath]);
|
|
10208
10480
|
} catch {
|
|
10209
|
-
|
|
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
|
|
10609
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
10338
10610
|
var versionMismatchWarned = false;
|
|
10339
10611
|
function getSdkVersion() {
|
|
10340
10612
|
try {
|
|
10341
|
-
const pkgPath =
|
|
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
|
|
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 =
|
|
10569
|
-
const path =
|
|
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,
|