craft-native 0.0.87 → 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/cli.js +75 -3
- package/dist/index.cjs +379 -101
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +367 -89
- 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";
|
|
@@ -9753,24 +9753,236 @@ var commonOptimizations = {
|
|
|
9753
9753
|
// src/updater/index.ts
|
|
9754
9754
|
var exports_updater = {};
|
|
9755
9755
|
__export(exports_updater, {
|
|
9756
|
+
verifyBundleTrust: () => verifyBundleTrust,
|
|
9756
9757
|
updaterCommand: () => updaterCommand,
|
|
9758
|
+
swapBundle: () => swapBundle,
|
|
9759
|
+
readBundleIdentity: () => readBundleIdentity,
|
|
9760
|
+
isMacOS: () => isMacOS,
|
|
9757
9761
|
generateUpdateManifest: () => generateUpdateManifest,
|
|
9762
|
+
extractBundleFromZip: () => extractBundleFromZip,
|
|
9763
|
+
extractBundleFromDmg: () => extractBundleFromDmg,
|
|
9764
|
+
extractBundle: () => extractBundle,
|
|
9765
|
+
dittoBundle: () => dittoBundle,
|
|
9758
9766
|
default: () => updater_default,
|
|
9767
|
+
clearQuarantine: () => clearQuarantine,
|
|
9768
|
+
canReplaceBundle: () => canReplaceBundle,
|
|
9759
9769
|
DeltaGenerator: () => DeltaGenerator,
|
|
9760
9770
|
AutoUpdater: () => AutoUpdater
|
|
9761
9771
|
});
|
|
9762
|
-
import { createReadStream, createWriteStream, existsSync as
|
|
9763
|
-
import { join as
|
|
9764
|
-
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";
|
|
9765
9776
|
import { createHash as createHash2 } from "crypto";
|
|
9766
9777
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
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);
|
|
9772
9982
|
}
|
|
9773
9983
|
|
|
9984
|
+
// src/updater/index.ts
|
|
9985
|
+
var PROGRESS_INTERVAL_MS = 100;
|
|
9774
9986
|
class AutoUpdater extends EventEmitter3 {
|
|
9775
9987
|
config;
|
|
9776
9988
|
updateInfo = null;
|
|
@@ -9779,12 +9991,22 @@ class AutoUpdater extends EventEmitter3 {
|
|
|
9779
9991
|
cachedEtag = null;
|
|
9780
9992
|
cachedLastModified = null;
|
|
9781
9993
|
cachedManifest = null;
|
|
9994
|
+
lastError = null;
|
|
9782
9995
|
emitError(error) {
|
|
9996
|
+
this.lastError = error instanceof Error ? error : new Error(String(error));
|
|
9783
9997
|
if (this.listenerCount("error") > 0)
|
|
9784
9998
|
this.emit("error", error);
|
|
9785
9999
|
else
|
|
9786
10000
|
console.error("[Updater]", error);
|
|
9787
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
|
+
}
|
|
9788
10010
|
constructor(config) {
|
|
9789
10011
|
super();
|
|
9790
10012
|
const url = config.updateUrl;
|
|
@@ -9818,6 +10040,7 @@ class AutoUpdater extends EventEmitter3 {
|
|
|
9818
10040
|
}
|
|
9819
10041
|
async checkForUpdates() {
|
|
9820
10042
|
this.emit("checking-for-update");
|
|
10043
|
+
this.lastError = null;
|
|
9821
10044
|
try {
|
|
9822
10045
|
const platform = this.getPlatform();
|
|
9823
10046
|
const url = `${this.config.updateUrl}?v=${this.config.currentVersion}&channel=${this.config.channel}&platform=${platform}`;
|
|
@@ -9866,20 +10089,21 @@ class AutoUpdater extends EventEmitter3 {
|
|
|
9866
10089
|
if (!this.updateInfo) {
|
|
9867
10090
|
throw new Error("No update available");
|
|
9868
10091
|
}
|
|
10092
|
+
this.lastError = null;
|
|
9869
10093
|
const platform = this.getPlatform();
|
|
9870
10094
|
const platformUpdate = this.updateInfo.platforms[platform];
|
|
9871
10095
|
if (!platformUpdate) {
|
|
9872
10096
|
throw new Error(`No update available for platform: ${platform}`);
|
|
9873
10097
|
}
|
|
9874
|
-
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;
|
|
9875
10099
|
const _updateSource = deltaUpdate || platformUpdate;
|
|
9876
10100
|
const url = deltaUpdate?.url || platformUpdate.url;
|
|
9877
10101
|
const expectedSize = deltaUpdate?.size || platformUpdate.size;
|
|
9878
10102
|
const expectedHash = deltaUpdate?.sha256 || platformUpdate.sha256;
|
|
9879
|
-
const downloadDir =
|
|
10103
|
+
const downloadDir = this.downloadDir();
|
|
9880
10104
|
mkdirSync2(downloadDir, { recursive: true });
|
|
9881
|
-
const fileName =
|
|
9882
|
-
this.downloadPath =
|
|
10105
|
+
const fileName = basename3(url);
|
|
10106
|
+
this.downloadPath = join3(downloadDir, fileName);
|
|
9883
10107
|
try {
|
|
9884
10108
|
const response = await fetch(url);
|
|
9885
10109
|
if (!response.ok) {
|
|
@@ -9894,39 +10118,59 @@ class AutoUpdater extends EventEmitter3 {
|
|
|
9894
10118
|
if (!reader) {
|
|
9895
10119
|
throw new Error("Failed to get response reader");
|
|
9896
10120
|
}
|
|
10121
|
+
const digest = createHash2("sha256");
|
|
10122
|
+
let lastProgressAt = 0;
|
|
9897
10123
|
while (true) {
|
|
9898
10124
|
const { done, value } = await reader.read();
|
|
9899
10125
|
if (done)
|
|
9900
10126
|
break;
|
|
9901
|
-
|
|
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
|
+
}
|
|
9902
10142
|
downloaded += value.length;
|
|
9903
|
-
const
|
|
9904
|
-
|
|
9905
|
-
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
|
|
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
|
+
}
|
|
9913
10157
|
}
|
|
9914
10158
|
fileStream.end();
|
|
9915
10159
|
await new Promise((resolve, reject) => {
|
|
9916
10160
|
fileStream.once("finish", resolve);
|
|
9917
10161
|
fileStream.once("error", reject);
|
|
9918
10162
|
});
|
|
9919
|
-
const hash =
|
|
10163
|
+
const hash = digest.digest("hex");
|
|
9920
10164
|
if (hash !== expectedHash) {
|
|
9921
10165
|
unlinkSync(this.downloadPath);
|
|
9922
10166
|
throw new Error("Download verification failed: hash mismatch");
|
|
9923
10167
|
}
|
|
9924
10168
|
if (deltaUpdate) {
|
|
9925
|
-
if (!
|
|
10169
|
+
if (!statSync2(this.config.appPath).isFile()) {
|
|
9926
10170
|
throw new Error("Delta updates require appPath to point to the installed bundle file");
|
|
9927
10171
|
}
|
|
9928
10172
|
const patchPath = this.downloadPath;
|
|
9929
|
-
const reconstructedPath =
|
|
10173
|
+
const reconstructedPath = join3(downloadDir, `reconstructed-${basename3(platformUpdate.url)}`);
|
|
9930
10174
|
await DeltaGenerator.apply(this.config.appPath, patchPath, reconstructedPath);
|
|
9931
10175
|
const reconstructedHash = await this.computeFileHash(reconstructedPath);
|
|
9932
10176
|
if (reconstructedHash !== platformUpdate.sha256) {
|
|
@@ -9964,7 +10208,7 @@ class AutoUpdater extends EventEmitter3 {
|
|
|
9964
10208
|
}
|
|
9965
10209
|
}
|
|
9966
10210
|
async installUpdate(restartAfter = true) {
|
|
9967
|
-
if (!this.downloadPath || !
|
|
10211
|
+
if (!this.downloadPath || !existsSync4(this.downloadPath)) {
|
|
9968
10212
|
throw new Error("No update downloaded");
|
|
9969
10213
|
}
|
|
9970
10214
|
if (this.updateInfo) {
|
|
@@ -10008,14 +10252,17 @@ class AutoUpdater extends EventEmitter3 {
|
|
|
10008
10252
|
await this.installLinuxUpdate();
|
|
10009
10253
|
break;
|
|
10010
10254
|
}
|
|
10011
|
-
if (this.downloadPath &&
|
|
10255
|
+
if (this.downloadPath && existsSync4(this.downloadPath)) {
|
|
10012
10256
|
unlinkSync(this.downloadPath);
|
|
10013
10257
|
}
|
|
10258
|
+
try {
|
|
10259
|
+
rmdirSync(this.downloadDir());
|
|
10260
|
+
} catch {}
|
|
10014
10261
|
progress.phase = "done";
|
|
10015
10262
|
progress.percent = 100;
|
|
10016
10263
|
this.emit("download-progress", progress);
|
|
10017
10264
|
if (restartAfter) {
|
|
10018
|
-
this.restartApp();
|
|
10265
|
+
await this.restartApp();
|
|
10019
10266
|
}
|
|
10020
10267
|
} catch (error) {
|
|
10021
10268
|
progress.phase = "error";
|
|
@@ -10026,38 +10273,45 @@ class AutoUpdater extends EventEmitter3 {
|
|
|
10026
10273
|
async installMacOSUpdate() {
|
|
10027
10274
|
const downloadPath = this.downloadPath;
|
|
10028
10275
|
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")) {
|
|
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 {
|
|
10054
10295
|
try {
|
|
10055
|
-
|
|
10056
|
-
} catch
|
|
10057
|
-
|
|
10058
|
-
|
|
10059
|
-
|
|
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.");
|
|
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}`);
|
|
10060
10313
|
}
|
|
10314
|
+
execFileSync2("sudo", ["installer", "-pkg", downloadPath, "-target", "/"]);
|
|
10061
10315
|
}
|
|
10062
10316
|
async installWindowsUpdate() {
|
|
10063
10317
|
const downloadPath = this.downloadPath;
|
|
@@ -10072,8 +10326,8 @@ class AutoUpdater extends EventEmitter3 {
|
|
|
10072
10326
|
stdio: "ignore"
|
|
10073
10327
|
});
|
|
10074
10328
|
} else if (downloadPath.endsWith(".zip")) {
|
|
10075
|
-
const appDir =
|
|
10076
|
-
|
|
10329
|
+
const appDir = dirname2(this.config.appPath);
|
|
10330
|
+
execFileSync2("powershell", [
|
|
10077
10331
|
"-NoProfile",
|
|
10078
10332
|
"-NonInteractive",
|
|
10079
10333
|
"-Command",
|
|
@@ -10086,28 +10340,29 @@ class AutoUpdater extends EventEmitter3 {
|
|
|
10086
10340
|
async installLinuxUpdate() {
|
|
10087
10341
|
const downloadPath = this.downloadPath;
|
|
10088
10342
|
if (downloadPath.endsWith(".AppImage")) {
|
|
10089
|
-
|
|
10090
|
-
const { renameSync } = await import("fs");
|
|
10091
|
-
|
|
10343
|
+
execFileSync2("chmod", ["+x", downloadPath]);
|
|
10344
|
+
const { renameSync: renameSync2 } = await import("fs");
|
|
10345
|
+
renameSync2(downloadPath, this.config.appPath);
|
|
10092
10346
|
} else if (downloadPath.endsWith(".deb")) {
|
|
10093
|
-
|
|
10347
|
+
execFileSync2("sudo", ["dpkg", "-i", downloadPath]);
|
|
10094
10348
|
} else if (downloadPath.endsWith(".rpm")) {
|
|
10095
|
-
|
|
10349
|
+
execFileSync2("sudo", ["rpm", "-U", downloadPath]);
|
|
10096
10350
|
} else if (downloadPath.endsWith(".tar.gz")) {
|
|
10097
|
-
const appDir =
|
|
10098
|
-
|
|
10351
|
+
const appDir = dirname2(this.config.appPath);
|
|
10352
|
+
execFileSync2("tar", ["-xzf", downloadPath, "-C", appDir]);
|
|
10099
10353
|
}
|
|
10100
10354
|
}
|
|
10101
|
-
restartApp() {
|
|
10102
|
-
const platform = this.getPlatform();
|
|
10355
|
+
async restartApp() {
|
|
10103
10356
|
const appPath = this.config.appPath;
|
|
10104
|
-
|
|
10357
|
+
if (this.config.relaunch) {
|
|
10358
|
+
await this.config.relaunch(appPath);
|
|
10359
|
+
return;
|
|
10360
|
+
}
|
|
10361
|
+
switch (this.getPlatform()) {
|
|
10105
10362
|
case "darwin":
|
|
10106
10363
|
spawn3("open", ["-n", appPath], { detached: true, stdio: "ignore" });
|
|
10107
10364
|
break;
|
|
10108
10365
|
case "win32":
|
|
10109
|
-
spawn3(appPath, [], { detached: true, stdio: "ignore" });
|
|
10110
|
-
break;
|
|
10111
10366
|
case "linux":
|
|
10112
10367
|
spawn3(appPath, [], { detached: true, stdio: "ignore" });
|
|
10113
10368
|
break;
|
|
@@ -10183,18 +10438,32 @@ class AutoUpdater extends EventEmitter3 {
|
|
|
10183
10438
|
}
|
|
10184
10439
|
|
|
10185
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
|
+
}
|
|
10186
10455
|
static async generate(oldPath, newPath, outputPath) {
|
|
10187
10456
|
try {
|
|
10188
|
-
|
|
10189
|
-
const stats =
|
|
10457
|
+
execFileSync2("bsdiff", [oldPath, newPath, outputPath]);
|
|
10458
|
+
const stats = statSync2(outputPath);
|
|
10190
10459
|
const hash = await DeltaGenerator.computeHash(outputPath);
|
|
10191
10460
|
return {
|
|
10192
10461
|
size: stats.size,
|
|
10193
10462
|
sha256: hash
|
|
10194
10463
|
};
|
|
10195
10464
|
} catch {
|
|
10196
|
-
|
|
10197
|
-
const stats =
|
|
10465
|
+
execFileSync2("xdelta3", ["-e", "-s", oldPath, newPath, outputPath]);
|
|
10466
|
+
const stats = statSync2(outputPath);
|
|
10198
10467
|
const hash = await DeltaGenerator.computeHash(outputPath);
|
|
10199
10468
|
return {
|
|
10200
10469
|
size: stats.size,
|
|
@@ -10204,9 +10473,9 @@ class DeltaGenerator {
|
|
|
10204
10473
|
}
|
|
10205
10474
|
static async apply(basePath, deltaPath, outputPath) {
|
|
10206
10475
|
try {
|
|
10207
|
-
|
|
10476
|
+
execFileSync2("bspatch", [basePath, outputPath, deltaPath]);
|
|
10208
10477
|
} catch {
|
|
10209
|
-
|
|
10478
|
+
execFileSync2("xdelta3", ["-d", "-s", basePath, deltaPath, outputPath]);
|
|
10210
10479
|
}
|
|
10211
10480
|
}
|
|
10212
10481
|
static async computeHash(filePath) {
|
|
@@ -10334,11 +10603,11 @@ Examples:
|
|
|
10334
10603
|
var updater_default = AutoUpdater;
|
|
10335
10604
|
|
|
10336
10605
|
// src/index.ts
|
|
10337
|
-
var
|
|
10606
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
10338
10607
|
var versionMismatchWarned = false;
|
|
10339
10608
|
function getSdkVersion() {
|
|
10340
10609
|
try {
|
|
10341
|
-
const pkgPath =
|
|
10610
|
+
const pkgPath = join4(import.meta.dir, "..", "package.json");
|
|
10342
10611
|
return JSON.parse(readFileSync3(pkgPath, "utf-8")).version ?? "unknown";
|
|
10343
10612
|
} catch {
|
|
10344
10613
|
return "unknown";
|
|
@@ -10348,7 +10617,7 @@ async function probeBinaryVersion(craftPath) {
|
|
|
10348
10617
|
if (versionMismatchWarned)
|
|
10349
10618
|
return;
|
|
10350
10619
|
try {
|
|
10351
|
-
const { stdout } = await
|
|
10620
|
+
const { stdout } = await execFileAsync2(craftPath, ["--version"], { timeout: 2000 });
|
|
10352
10621
|
const nativeVersion = parseCraftVersionOutput(stdout);
|
|
10353
10622
|
const sdkVersion = getSdkVersion();
|
|
10354
10623
|
if (sdkVersion !== "unknown" && nativeVersion && nativeVersion !== sdkVersion) {
|
|
@@ -10565,8 +10834,8 @@ class CraftApp {
|
|
|
10565
10834
|
if (safeForArgv) {
|
|
10566
10835
|
args.push("--sidebar-config", json);
|
|
10567
10836
|
} else {
|
|
10568
|
-
const dir =
|
|
10569
|
-
const path =
|
|
10837
|
+
const dir = mkdtempSync3(join4(tmpdir4(), "craft-sidebar-"));
|
|
10838
|
+
const path = join4(dir, "sidebar-config.json");
|
|
10570
10839
|
writeFileSync3(path, json, "utf-8");
|
|
10571
10840
|
args.push("--sidebar-config-file", path);
|
|
10572
10841
|
}
|
|
@@ -10605,6 +10874,7 @@ export {
|
|
|
10605
10874
|
watch,
|
|
10606
10875
|
vueOptimizations,
|
|
10607
10876
|
verifyPassword,
|
|
10877
|
+
verifyBundleTrust,
|
|
10608
10878
|
variants,
|
|
10609
10879
|
uuid,
|
|
10610
10880
|
exports_updater as updater,
|
|
@@ -10623,6 +10893,7 @@ export {
|
|
|
10623
10893
|
tahoeStyles,
|
|
10624
10894
|
tahoeStyle,
|
|
10625
10895
|
tahoeDemoData,
|
|
10896
|
+
swapBundle,
|
|
10626
10897
|
svelteOptimizations,
|
|
10627
10898
|
submenu,
|
|
10628
10899
|
styles,
|
|
@@ -10670,6 +10941,7 @@ export {
|
|
|
10670
10941
|
registerShortcut,
|
|
10671
10942
|
readText,
|
|
10672
10943
|
readHTML,
|
|
10944
|
+
readBundleIdentity,
|
|
10673
10945
|
readBinaryFile,
|
|
10674
10946
|
reactOptimizations,
|
|
10675
10947
|
randomString,
|
|
@@ -10737,9 +11009,13 @@ export {
|
|
|
10737
11009
|
foregroundService,
|
|
10738
11010
|
focusFilters,
|
|
10739
11011
|
focus,
|
|
11012
|
+
extractBundleFromZip,
|
|
11013
|
+
extractBundleFromDmg,
|
|
11014
|
+
extractBundle,
|
|
10740
11015
|
exit,
|
|
10741
11016
|
exec,
|
|
10742
11017
|
env,
|
|
11018
|
+
dittoBundle,
|
|
10743
11019
|
dialog,
|
|
10744
11020
|
device,
|
|
10745
11021
|
desktopWidgets,
|
|
@@ -10770,8 +11046,10 @@ export {
|
|
|
10770
11046
|
commonOptimizations,
|
|
10771
11047
|
cx as clsx,
|
|
10772
11048
|
clipboard,
|
|
11049
|
+
clearQuarantine,
|
|
10773
11050
|
checkbox,
|
|
10774
11051
|
carplay,
|
|
11052
|
+
canReplaceBundle,
|
|
10775
11053
|
canQuickLook,
|
|
10776
11054
|
camera,
|
|
10777
11055
|
buildMenu,
|