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.cjs
CHANGED
|
@@ -52,6 +52,7 @@ __export(exports_src, {
|
|
|
52
52
|
watch: () => watch,
|
|
53
53
|
vueOptimizations: () => vueOptimizations,
|
|
54
54
|
verifyPassword: () => verifyPassword,
|
|
55
|
+
verifyBundleTrust: () => verifyBundleTrust,
|
|
55
56
|
variants: () => variants,
|
|
56
57
|
uuid: () => uuid,
|
|
57
58
|
updater: () => exports_updater,
|
|
@@ -70,6 +71,7 @@ __export(exports_src, {
|
|
|
70
71
|
tahoeStyles: () => tahoeStyles,
|
|
71
72
|
tahoeStyle: () => tahoeStyle,
|
|
72
73
|
tahoeDemoData: () => tahoeDemoData,
|
|
74
|
+
swapBundle: () => swapBundle,
|
|
73
75
|
svelteOptimizations: () => svelteOptimizations,
|
|
74
76
|
submenu: () => submenu,
|
|
75
77
|
styles: () => styles,
|
|
@@ -117,6 +119,7 @@ __export(exports_src, {
|
|
|
117
119
|
registerShortcut: () => registerShortcut,
|
|
118
120
|
readText: () => readText,
|
|
119
121
|
readHTML: () => readHTML,
|
|
122
|
+
readBundleIdentity: () => readBundleIdentity,
|
|
120
123
|
readBinaryFile: () => readBinaryFile,
|
|
121
124
|
reactOptimizations: () => reactOptimizations,
|
|
122
125
|
randomString: () => randomString,
|
|
@@ -184,9 +187,13 @@ __export(exports_src, {
|
|
|
184
187
|
foregroundService: () => foregroundService,
|
|
185
188
|
focusFilters: () => focusFilters,
|
|
186
189
|
focus: () => focus,
|
|
190
|
+
extractBundleFromZip: () => extractBundleFromZip,
|
|
191
|
+
extractBundleFromDmg: () => extractBundleFromDmg,
|
|
192
|
+
extractBundle: () => extractBundle,
|
|
187
193
|
exit: () => exit,
|
|
188
194
|
exec: () => exec,
|
|
189
195
|
env: () => env,
|
|
196
|
+
dittoBundle: () => dittoBundle,
|
|
190
197
|
dialog: () => dialog,
|
|
191
198
|
device: () => device,
|
|
192
199
|
desktopWidgets: () => desktopWidgets,
|
|
@@ -217,8 +224,10 @@ __export(exports_src, {
|
|
|
217
224
|
commonOptimizations: () => commonOptimizations,
|
|
218
225
|
clsx: () => cx,
|
|
219
226
|
clipboard: () => clipboard,
|
|
227
|
+
clearQuarantine: () => clearQuarantine,
|
|
220
228
|
checkbox: () => checkbox,
|
|
221
229
|
carplay: () => carplay,
|
|
230
|
+
canReplaceBundle: () => canReplaceBundle,
|
|
222
231
|
canQuickLook: () => canQuickLook,
|
|
223
232
|
camera: () => camera,
|
|
224
233
|
buildMenu: () => buildMenu,
|
|
@@ -2532,6 +2541,9 @@ class Window {
|
|
|
2532
2541
|
async setVibrancy(vibrancy) {
|
|
2533
2542
|
await this._call("setVibrancy", { vibrancy });
|
|
2534
2543
|
}
|
|
2544
|
+
async setAppearance(appearance) {
|
|
2545
|
+
await this._call("setAppearance", { appearance });
|
|
2546
|
+
}
|
|
2535
2547
|
get windowControls() {
|
|
2536
2548
|
return globalThis.craft?.windowControls;
|
|
2537
2549
|
}
|
|
@@ -2631,6 +2643,7 @@ class WindowManager {
|
|
|
2631
2643
|
setOpacity = (opacity) => this.current.setOpacity(opacity);
|
|
2632
2644
|
setBackgroundColor = (color) => this.current.setBackgroundColor(color);
|
|
2633
2645
|
setVibrancy = (vibrancy) => this.current.setVibrancy(vibrancy);
|
|
2646
|
+
setAppearance = (appearance) => this.current.setAppearance(appearance);
|
|
2634
2647
|
setResizable = (resizable) => this.current.setResizable(resizable);
|
|
2635
2648
|
startDrag = () => this.current.startDrag();
|
|
2636
2649
|
getState = () => this.current.getState();
|
|
@@ -9979,24 +9992,236 @@ var commonOptimizations = {
|
|
|
9979
9992
|
// src/updater/index.ts
|
|
9980
9993
|
var exports_updater = {};
|
|
9981
9994
|
__export(exports_updater, {
|
|
9995
|
+
verifyBundleTrust: () => verifyBundleTrust,
|
|
9982
9996
|
updaterCommand: () => updaterCommand,
|
|
9997
|
+
swapBundle: () => swapBundle,
|
|
9998
|
+
readBundleIdentity: () => readBundleIdentity,
|
|
9999
|
+
isMacOS: () => isMacOS,
|
|
9983
10000
|
generateUpdateManifest: () => generateUpdateManifest,
|
|
10001
|
+
extractBundleFromZip: () => extractBundleFromZip,
|
|
10002
|
+
extractBundleFromDmg: () => extractBundleFromDmg,
|
|
10003
|
+
extractBundle: () => extractBundle,
|
|
10004
|
+
dittoBundle: () => dittoBundle,
|
|
9984
10005
|
default: () => updater_default,
|
|
10006
|
+
clearQuarantine: () => clearQuarantine,
|
|
10007
|
+
canReplaceBundle: () => canReplaceBundle,
|
|
9985
10008
|
DeltaGenerator: () => DeltaGenerator,
|
|
9986
10009
|
AutoUpdater: () => AutoUpdater
|
|
9987
10010
|
});
|
|
9988
|
-
var
|
|
9989
|
-
var
|
|
9990
|
-
var
|
|
10011
|
+
var import_fs4 = require("fs");
|
|
10012
|
+
var import_path3 = require("path");
|
|
10013
|
+
var import_os3 = require("os");
|
|
10014
|
+
var import_child_process3 = require("child_process");
|
|
9991
10015
|
var import_crypto3 = require("crypto");
|
|
9992
10016
|
var import_events3 = require("events");
|
|
9993
|
-
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
|
|
10017
|
+
|
|
10018
|
+
// src/updater/macos-bundle.ts
|
|
10019
|
+
var import_child_process2 = require("child_process");
|
|
10020
|
+
var import_fs3 = require("fs");
|
|
10021
|
+
var import_os2 = require("os");
|
|
10022
|
+
var import_path2 = require("path");
|
|
10023
|
+
var import_util = require("util");
|
|
10024
|
+
var execFileAsync = import_util.promisify(import_child_process2.execFile);
|
|
10025
|
+
function isMacOS() {
|
|
10026
|
+
return process.platform === "darwin";
|
|
10027
|
+
}
|
|
10028
|
+
async function readBundleIdentity(appPath) {
|
|
10029
|
+
const empty = { identifier: null, teamId: null, authority: null };
|
|
10030
|
+
if (!isMacOS())
|
|
10031
|
+
return empty;
|
|
10032
|
+
let report;
|
|
10033
|
+
try {
|
|
10034
|
+
const { stderr, stdout } = await execFileAsync("codesign", ["-dv", "--verbose=4", appPath]);
|
|
10035
|
+
report = `${stderr}
|
|
10036
|
+
${stdout}`;
|
|
10037
|
+
} catch (error) {
|
|
10038
|
+
const e = error;
|
|
10039
|
+
report = `${e.stderr ?? ""}
|
|
10040
|
+
${e.stdout ?? ""}`;
|
|
10041
|
+
if (!report.trim())
|
|
10042
|
+
return empty;
|
|
10043
|
+
}
|
|
10044
|
+
const field = (name) => {
|
|
10045
|
+
const match = report.match(new RegExp(`^${name}=(.+)$`, "m"));
|
|
10046
|
+
if (!match)
|
|
10047
|
+
return null;
|
|
10048
|
+
const value = match[1].trim();
|
|
10049
|
+
return value === "not set" || value.length === 0 ? null : value;
|
|
10050
|
+
};
|
|
10051
|
+
return {
|
|
10052
|
+
identifier: field("Identifier"),
|
|
10053
|
+
teamId: field("TeamIdentifier"),
|
|
10054
|
+
authority: field("Authority")
|
|
10055
|
+
};
|
|
10056
|
+
}
|
|
10057
|
+
async function verifyBundleTrust(appPath, policy = {}) {
|
|
10058
|
+
const identity = await readBundleIdentity(appPath);
|
|
10059
|
+
const requireNotarized = policy.requireNotarized ?? true;
|
|
10060
|
+
if (!import_fs3.existsSync(appPath)) {
|
|
10061
|
+
return { ok: false, identity, gatekeeperSource: null, reason: "unreadable", detail: `No such bundle: ${appPath}` };
|
|
10062
|
+
}
|
|
10063
|
+
try {
|
|
10064
|
+
await execFileAsync("codesign", ["--verify", "--deep", "--strict", "--verbose=2", appPath]);
|
|
10065
|
+
} catch (error) {
|
|
10066
|
+
return {
|
|
10067
|
+
ok: false,
|
|
10068
|
+
identity,
|
|
10069
|
+
gatekeeperSource: null,
|
|
10070
|
+
reason: "codesign-invalid",
|
|
10071
|
+
detail: describe(error)
|
|
10072
|
+
};
|
|
10073
|
+
}
|
|
10074
|
+
let gatekeeperSource = null;
|
|
10075
|
+
if (requireNotarized) {
|
|
10076
|
+
try {
|
|
10077
|
+
const { stderr, stdout } = await execFileAsync("spctl", ["-a", "-t", "exec", "-vv", appPath]);
|
|
10078
|
+
const report = `${stderr}
|
|
10079
|
+
${stdout}`;
|
|
10080
|
+
gatekeeperSource = report.match(/^source=(.+)$/m)?.[1]?.trim() ?? null;
|
|
10081
|
+
} catch (error) {
|
|
10082
|
+
return {
|
|
10083
|
+
ok: false,
|
|
10084
|
+
identity,
|
|
10085
|
+
gatekeeperSource: null,
|
|
10086
|
+
reason: "gatekeeper-rejected",
|
|
10087
|
+
detail: describe(error)
|
|
10088
|
+
};
|
|
10089
|
+
}
|
|
10090
|
+
}
|
|
10091
|
+
if (policy.teamId && identity.teamId !== policy.teamId) {
|
|
10092
|
+
return {
|
|
10093
|
+
ok: false,
|
|
10094
|
+
identity,
|
|
10095
|
+
gatekeeperSource,
|
|
10096
|
+
reason: "team-mismatch",
|
|
10097
|
+
detail: `Expected Team ID ${policy.teamId}, bundle is signed by ${identity.teamId ?? "nobody"}`
|
|
10098
|
+
};
|
|
10099
|
+
}
|
|
10100
|
+
return { ok: true, identity, gatekeeperSource };
|
|
10101
|
+
}
|
|
10102
|
+
async function clearQuarantine(appPath) {
|
|
10103
|
+
if (!isMacOS())
|
|
10104
|
+
return;
|
|
10105
|
+
try {
|
|
10106
|
+
await execFileAsync("xattr", ["-d", "-r", "com.apple.quarantine", appPath]);
|
|
10107
|
+
} catch {}
|
|
10108
|
+
}
|
|
10109
|
+
async function dittoBundle(source, destination) {
|
|
10110
|
+
await execFileAsync("ditto", [source, destination]);
|
|
10111
|
+
}
|
|
10112
|
+
function findTopLevelApp(root) {
|
|
10113
|
+
const out = import_child_process2.execFileSync("find", [root, "-maxdepth", "2", "-name", "*.app", "-type", "d", "-print0"]);
|
|
10114
|
+
const parts = out.toString("utf-8").split("\x00").filter((s) => s.length > 0);
|
|
10115
|
+
parts.sort((a, b) => a.split("/").length - b.split("/").length);
|
|
10116
|
+
return parts[0] ?? null;
|
|
10117
|
+
}
|
|
10118
|
+
async function extractBundleFromDmg(dmgPath, stagingDir) {
|
|
10119
|
+
const staging = stagingDir ?? import_fs3.mkdtempSync(import_path2.join(import_os2.tmpdir(), "craft-update-"));
|
|
10120
|
+
const mountPoint = import_fs3.mkdtempSync(import_path2.join(import_os2.tmpdir(), "craft-mount-"));
|
|
10121
|
+
let mounted = false;
|
|
10122
|
+
try {
|
|
10123
|
+
await execFileAsync("hdiutil", ["attach", dmgPath, "-nobrowse", "-readonly", "-mountpoint", mountPoint]);
|
|
10124
|
+
mounted = true;
|
|
10125
|
+
const source = findTopLevelApp(mountPoint);
|
|
10126
|
+
if (!source)
|
|
10127
|
+
throw new Error(`No .app found in disk image: ${dmgPath}`);
|
|
10128
|
+
const appPath = import_path2.join(staging, import_path2.basename(source));
|
|
10129
|
+
await dittoBundle(source, appPath);
|
|
10130
|
+
return { appPath, stagingDir: staging };
|
|
10131
|
+
} finally {
|
|
10132
|
+
if (mounted) {
|
|
10133
|
+
try {
|
|
10134
|
+
await execFileAsync("hdiutil", ["detach", mountPoint, "-force"]);
|
|
10135
|
+
} catch {}
|
|
10136
|
+
}
|
|
10137
|
+
try {
|
|
10138
|
+
import_fs3.rmSync(mountPoint, { recursive: true, force: true });
|
|
10139
|
+
} catch {}
|
|
10140
|
+
}
|
|
10141
|
+
}
|
|
10142
|
+
async function extractBundleFromZip(zipPath, stagingDir) {
|
|
10143
|
+
const staging = stagingDir ?? import_fs3.mkdtempSync(import_path2.join(import_os2.tmpdir(), "craft-update-"));
|
|
10144
|
+
const unpacked = import_path2.join(staging, "payload");
|
|
10145
|
+
await execFileAsync("ditto", ["-x", "-k", zipPath, unpacked]);
|
|
10146
|
+
const source = findTopLevelApp(unpacked);
|
|
10147
|
+
if (!source)
|
|
10148
|
+
throw new Error(`No .app found in archive: ${zipPath}`);
|
|
10149
|
+
return { appPath: source, stagingDir: staging };
|
|
10150
|
+
}
|
|
10151
|
+
async function extractBundle(archivePath, stagingDir) {
|
|
10152
|
+
if (archivePath.endsWith(".dmg"))
|
|
10153
|
+
return extractBundleFromDmg(archivePath, stagingDir);
|
|
10154
|
+
if (archivePath.endsWith(".zip"))
|
|
10155
|
+
return extractBundleFromZip(archivePath, stagingDir);
|
|
10156
|
+
throw new Error(`Cannot extract an app bundle from ${archivePath}`);
|
|
10157
|
+
}
|
|
10158
|
+
async function swapBundle(stagedPath, installedPath) {
|
|
10159
|
+
if (!import_fs3.existsSync(stagedPath))
|
|
10160
|
+
throw new Error(`Staged bundle is missing: ${stagedPath}`);
|
|
10161
|
+
const parent = import_path2.dirname(installedPath);
|
|
10162
|
+
const stamp = `${Date.now()}.${process.pid}`;
|
|
10163
|
+
const incoming = import_path2.join(parent, `.${import_path2.basename(installedPath)}.${stamp}.incoming`);
|
|
10164
|
+
const retired = import_path2.join(parent, `.${import_path2.basename(installedPath)}.${stamp}.retired`);
|
|
10165
|
+
try {
|
|
10166
|
+
import_fs3.renameSync(stagedPath, incoming);
|
|
10167
|
+
} catch {
|
|
10168
|
+
await dittoBundle(stagedPath, incoming);
|
|
10169
|
+
}
|
|
10170
|
+
const hadPrevious = import_fs3.existsSync(installedPath);
|
|
10171
|
+
try {
|
|
10172
|
+
if (hadPrevious)
|
|
10173
|
+
import_fs3.renameSync(installedPath, retired);
|
|
10174
|
+
try {
|
|
10175
|
+
import_fs3.renameSync(incoming, installedPath);
|
|
10176
|
+
} catch (error) {
|
|
10177
|
+
if (hadPrevious) {
|
|
10178
|
+
try {
|
|
10179
|
+
import_fs3.renameSync(retired, installedPath);
|
|
10180
|
+
} catch {}
|
|
10181
|
+
}
|
|
10182
|
+
throw error;
|
|
10183
|
+
}
|
|
10184
|
+
} catch (error) {
|
|
10185
|
+
try {
|
|
10186
|
+
import_fs3.rmSync(incoming, { recursive: true, force: true });
|
|
10187
|
+
} catch {}
|
|
10188
|
+
throw error;
|
|
10189
|
+
}
|
|
10190
|
+
if (hadPrevious) {
|
|
10191
|
+
try {
|
|
10192
|
+
import_fs3.rmSync(retired, { recursive: true, force: true });
|
|
10193
|
+
return { previousPath: null };
|
|
10194
|
+
} catch {
|
|
10195
|
+
return { previousPath: retired };
|
|
10196
|
+
}
|
|
10197
|
+
}
|
|
10198
|
+
return { previousPath: null };
|
|
10199
|
+
}
|
|
10200
|
+
function canReplaceBundle(installedPath) {
|
|
10201
|
+
try {
|
|
10202
|
+
const parent = import_path2.dirname(installedPath);
|
|
10203
|
+
import_fs3.accessSync(parent, import_fs3.constants.W_OK);
|
|
10204
|
+
if (import_fs3.existsSync(installedPath))
|
|
10205
|
+
import_fs3.statSync(installedPath);
|
|
10206
|
+
return true;
|
|
10207
|
+
} catch {
|
|
10208
|
+
return false;
|
|
10209
|
+
}
|
|
10210
|
+
}
|
|
10211
|
+
function describe(error) {
|
|
10212
|
+
if (error && typeof error === "object") {
|
|
10213
|
+
const e = error;
|
|
10214
|
+
const stderr = e.stderr?.trim();
|
|
10215
|
+
if (stderr)
|
|
10216
|
+
return stderr;
|
|
10217
|
+
if (e.message)
|
|
10218
|
+
return e.message;
|
|
10219
|
+
}
|
|
10220
|
+
return String(error);
|
|
9998
10221
|
}
|
|
9999
10222
|
|
|
10223
|
+
// src/updater/index.ts
|
|
10224
|
+
var PROGRESS_INTERVAL_MS = 100;
|
|
10000
10225
|
class AutoUpdater extends import_events3.EventEmitter {
|
|
10001
10226
|
config;
|
|
10002
10227
|
updateInfo = null;
|
|
@@ -10005,12 +10230,22 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10005
10230
|
cachedEtag = null;
|
|
10006
10231
|
cachedLastModified = null;
|
|
10007
10232
|
cachedManifest = null;
|
|
10233
|
+
lastError = null;
|
|
10008
10234
|
emitError(error) {
|
|
10235
|
+
this.lastError = error instanceof Error ? error : new Error(String(error));
|
|
10009
10236
|
if (this.listenerCount("error") > 0)
|
|
10010
10237
|
this.emit("error", error);
|
|
10011
10238
|
else
|
|
10012
10239
|
console.error("[Updater]", error);
|
|
10013
10240
|
}
|
|
10241
|
+
downloadDir() {
|
|
10242
|
+
if (this.config.downloadDir)
|
|
10243
|
+
return this.config.downloadDir;
|
|
10244
|
+
return import_path3.join(import_os3.tmpdir(), "craft-updates", import_path3.basename(this.config.appPath) || "app");
|
|
10245
|
+
}
|
|
10246
|
+
getLastError() {
|
|
10247
|
+
return this.lastError;
|
|
10248
|
+
}
|
|
10014
10249
|
constructor(config) {
|
|
10015
10250
|
super();
|
|
10016
10251
|
const url = config.updateUrl;
|
|
@@ -10044,6 +10279,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10044
10279
|
}
|
|
10045
10280
|
async checkForUpdates() {
|
|
10046
10281
|
this.emit("checking-for-update");
|
|
10282
|
+
this.lastError = null;
|
|
10047
10283
|
try {
|
|
10048
10284
|
const platform = this.getPlatform();
|
|
10049
10285
|
const url = `${this.config.updateUrl}?v=${this.config.currentVersion}&channel=${this.config.channel}&platform=${platform}`;
|
|
@@ -10092,20 +10328,21 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10092
10328
|
if (!this.updateInfo) {
|
|
10093
10329
|
throw new Error("No update available");
|
|
10094
10330
|
}
|
|
10331
|
+
this.lastError = null;
|
|
10095
10332
|
const platform = this.getPlatform();
|
|
10096
10333
|
const platformUpdate = this.updateInfo.platforms[platform];
|
|
10097
10334
|
if (!platformUpdate) {
|
|
10098
10335
|
throw new Error(`No update available for platform: ${platform}`);
|
|
10099
10336
|
}
|
|
10100
|
-
const deltaUpdate = platformUpdate.delta?.find((d) => d.fromVersion === this.config.currentVersion);
|
|
10337
|
+
const deltaUpdate = DeltaGenerator.isSupported() ? platformUpdate.delta?.find((d) => d.fromVersion === this.config.currentVersion) : undefined;
|
|
10101
10338
|
const _updateSource = deltaUpdate || platformUpdate;
|
|
10102
10339
|
const url = deltaUpdate?.url || platformUpdate.url;
|
|
10103
10340
|
const expectedSize = deltaUpdate?.size || platformUpdate.size;
|
|
10104
10341
|
const expectedHash = deltaUpdate?.sha256 || platformUpdate.sha256;
|
|
10105
|
-
const downloadDir =
|
|
10106
|
-
|
|
10107
|
-
const fileName =
|
|
10108
|
-
this.downloadPath =
|
|
10342
|
+
const downloadDir = this.downloadDir();
|
|
10343
|
+
import_fs4.mkdirSync(downloadDir, { recursive: true });
|
|
10344
|
+
const fileName = import_path3.basename(url);
|
|
10345
|
+
this.downloadPath = import_path3.join(downloadDir, fileName);
|
|
10109
10346
|
try {
|
|
10110
10347
|
const response = await fetch(url);
|
|
10111
10348
|
if (!response.ok) {
|
|
@@ -10115,63 +10352,83 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10115
10352
|
const total = contentLength || expectedSize;
|
|
10116
10353
|
let downloaded = 0;
|
|
10117
10354
|
const startTime = Date.now();
|
|
10118
|
-
const fileStream =
|
|
10355
|
+
const fileStream = import_fs4.createWriteStream(this.downloadPath);
|
|
10119
10356
|
const reader = response.body?.getReader();
|
|
10120
10357
|
if (!reader) {
|
|
10121
10358
|
throw new Error("Failed to get response reader");
|
|
10122
10359
|
}
|
|
10360
|
+
const digest = import_crypto3.createHash("sha256");
|
|
10361
|
+
let lastProgressAt = 0;
|
|
10123
10362
|
while (true) {
|
|
10124
10363
|
const { done, value } = await reader.read();
|
|
10125
10364
|
if (done)
|
|
10126
10365
|
break;
|
|
10127
|
-
|
|
10366
|
+
digest.update(value);
|
|
10367
|
+
if (!fileStream.write(value)) {
|
|
10368
|
+
await new Promise((resolve, reject) => {
|
|
10369
|
+
const onDrain = () => {
|
|
10370
|
+
fileStream.off("error", onError);
|
|
10371
|
+
resolve();
|
|
10372
|
+
};
|
|
10373
|
+
const onError = (error) => {
|
|
10374
|
+
fileStream.off("drain", onDrain);
|
|
10375
|
+
reject(error);
|
|
10376
|
+
};
|
|
10377
|
+
fileStream.once("drain", onDrain);
|
|
10378
|
+
fileStream.once("error", onError);
|
|
10379
|
+
});
|
|
10380
|
+
}
|
|
10128
10381
|
downloaded += value.length;
|
|
10129
|
-
const
|
|
10130
|
-
|
|
10131
|
-
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10382
|
+
const now = Date.now();
|
|
10383
|
+
if (now - lastProgressAt >= PROGRESS_INTERVAL_MS || downloaded === total) {
|
|
10384
|
+
lastProgressAt = now;
|
|
10385
|
+
const elapsed = (now - startTime) / 1000;
|
|
10386
|
+
const speed = elapsed > 0 ? downloaded / elapsed : 0;
|
|
10387
|
+
const progress = {
|
|
10388
|
+
phase: "downloading",
|
|
10389
|
+
percent: total > 0 ? Math.round(downloaded / total * 100) : 0,
|
|
10390
|
+
bytesDownloaded: downloaded,
|
|
10391
|
+
bytesTotal: total,
|
|
10392
|
+
speed
|
|
10393
|
+
};
|
|
10394
|
+
this.emit("download-progress", progress);
|
|
10395
|
+
}
|
|
10139
10396
|
}
|
|
10140
10397
|
fileStream.end();
|
|
10141
10398
|
await new Promise((resolve, reject) => {
|
|
10142
10399
|
fileStream.once("finish", resolve);
|
|
10143
10400
|
fileStream.once("error", reject);
|
|
10144
10401
|
});
|
|
10145
|
-
const hash =
|
|
10402
|
+
const hash = digest.digest("hex");
|
|
10146
10403
|
if (hash !== expectedHash) {
|
|
10147
|
-
|
|
10404
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10148
10405
|
throw new Error("Download verification failed: hash mismatch");
|
|
10149
10406
|
}
|
|
10150
10407
|
if (deltaUpdate) {
|
|
10151
|
-
if (!
|
|
10408
|
+
if (!import_fs4.statSync(this.config.appPath).isFile()) {
|
|
10152
10409
|
throw new Error("Delta updates require appPath to point to the installed bundle file");
|
|
10153
10410
|
}
|
|
10154
10411
|
const patchPath = this.downloadPath;
|
|
10155
|
-
const reconstructedPath =
|
|
10412
|
+
const reconstructedPath = import_path3.join(downloadDir, `reconstructed-${import_path3.basename(platformUpdate.url)}`);
|
|
10156
10413
|
await DeltaGenerator.apply(this.config.appPath, patchPath, reconstructedPath);
|
|
10157
10414
|
const reconstructedHash = await this.computeFileHash(reconstructedPath);
|
|
10158
10415
|
if (reconstructedHash !== platformUpdate.sha256) {
|
|
10159
10416
|
try {
|
|
10160
|
-
|
|
10417
|
+
import_fs4.unlinkSync(reconstructedPath);
|
|
10161
10418
|
} catch {}
|
|
10162
10419
|
throw new Error("Delta reconstruction failed: full bundle hash mismatch");
|
|
10163
10420
|
}
|
|
10164
|
-
|
|
10421
|
+
import_fs4.unlinkSync(patchPath);
|
|
10165
10422
|
this.downloadPath = reconstructedPath;
|
|
10166
10423
|
}
|
|
10167
10424
|
if (this.config.publicKeyPem && !platformUpdate.signature) {
|
|
10168
|
-
|
|
10425
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10169
10426
|
throw new Error("Updater: publicKeyPem is configured but the manifest entry has no signature. " + "Refusing to install unsigned update.");
|
|
10170
10427
|
}
|
|
10171
10428
|
if (platformUpdate.signature) {
|
|
10172
10429
|
const valid = await this.verifySignature(this.downloadPath, platformUpdate.signature);
|
|
10173
10430
|
if (!valid) {
|
|
10174
|
-
|
|
10431
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10175
10432
|
throw new Error("Download verification failed: invalid signature");
|
|
10176
10433
|
}
|
|
10177
10434
|
}
|
|
@@ -10190,7 +10447,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10190
10447
|
}
|
|
10191
10448
|
}
|
|
10192
10449
|
async installUpdate(restartAfter = true) {
|
|
10193
|
-
if (!this.downloadPath || !
|
|
10450
|
+
if (!this.downloadPath || !import_fs4.existsSync(this.downloadPath)) {
|
|
10194
10451
|
throw new Error("No update downloaded");
|
|
10195
10452
|
}
|
|
10196
10453
|
if (this.updateInfo) {
|
|
@@ -10200,7 +10457,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10200
10457
|
const hash = await this.computeFileHash(this.downloadPath);
|
|
10201
10458
|
if (hash !== platformUpdate.sha256) {
|
|
10202
10459
|
try {
|
|
10203
|
-
|
|
10460
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10204
10461
|
} catch {}
|
|
10205
10462
|
throw new Error(`Updater.installUpdate: pre-install hash mismatch on ${this.downloadPath}. ` + "The downloaded bundle changed between download and install — refusing to run installer.");
|
|
10206
10463
|
}
|
|
@@ -10211,7 +10468,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10211
10468
|
const valid = await this.verifySignature(this.downloadPath, platformUpdate.signature);
|
|
10212
10469
|
if (!valid) {
|
|
10213
10470
|
try {
|
|
10214
|
-
|
|
10471
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10215
10472
|
} catch {}
|
|
10216
10473
|
throw new Error("Updater.installUpdate: invalid signature on downloaded bundle");
|
|
10217
10474
|
}
|
|
@@ -10234,14 +10491,17 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10234
10491
|
await this.installLinuxUpdate();
|
|
10235
10492
|
break;
|
|
10236
10493
|
}
|
|
10237
|
-
if (this.downloadPath &&
|
|
10238
|
-
|
|
10494
|
+
if (this.downloadPath && import_fs4.existsSync(this.downloadPath)) {
|
|
10495
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10239
10496
|
}
|
|
10497
|
+
try {
|
|
10498
|
+
import_fs4.rmdirSync(this.downloadDir());
|
|
10499
|
+
} catch {}
|
|
10240
10500
|
progress.phase = "done";
|
|
10241
10501
|
progress.percent = 100;
|
|
10242
10502
|
this.emit("download-progress", progress);
|
|
10243
10503
|
if (restartAfter) {
|
|
10244
|
-
this.restartApp();
|
|
10504
|
+
await this.restartApp();
|
|
10245
10505
|
}
|
|
10246
10506
|
} catch (error) {
|
|
10247
10507
|
progress.phase = "error";
|
|
@@ -10252,54 +10512,61 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10252
10512
|
async installMacOSUpdate() {
|
|
10253
10513
|
const downloadPath = this.downloadPath;
|
|
10254
10514
|
const appPath = this.config.appPath;
|
|
10255
|
-
if (downloadPath.endsWith(".
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
rmSync2(appPath, { recursive: true, force: true });
|
|
10275
|
-
cpSync2(dmgApp, appPath, { recursive: true });
|
|
10276
|
-
}
|
|
10277
|
-
import_child_process2.execFileSync("hdiutil", ["detach", mountPoint]);
|
|
10278
|
-
}
|
|
10279
|
-
} else if (downloadPath.endsWith(".pkg")) {
|
|
10515
|
+
if (downloadPath.endsWith(".pkg")) {
|
|
10516
|
+
await this.installMacOSPackage(downloadPath);
|
|
10517
|
+
return;
|
|
10518
|
+
}
|
|
10519
|
+
if (!canReplaceBundle(appPath)) {
|
|
10520
|
+
throw new Error(`Cannot replace ${appPath}: ${import_path3.dirname(appPath)} is not writable by this user. ` + "Install the update manually, or move the app somewhere you own.");
|
|
10521
|
+
}
|
|
10522
|
+
const staged = await extractBundle(downloadPath);
|
|
10523
|
+
try {
|
|
10524
|
+
const trust = await this.assertBundleTrusted(staged.appPath);
|
|
10525
|
+
await clearQuarantine(staged.appPath);
|
|
10526
|
+
const { previousPath } = await swapBundle(staged.appPath, appPath);
|
|
10527
|
+
this.emit("update-installed", {
|
|
10528
|
+
path: appPath,
|
|
10529
|
+
version: this.updateInfo?.version,
|
|
10530
|
+
identity: trust.identity,
|
|
10531
|
+
leftoverPath: previousPath
|
|
10532
|
+
});
|
|
10533
|
+
} finally {
|
|
10280
10534
|
try {
|
|
10281
|
-
|
|
10282
|
-
} catch
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
|
|
10535
|
+
import_fs4.rmSync(staged.stagingDir, { recursive: true, force: true });
|
|
10536
|
+
} catch {}
|
|
10537
|
+
}
|
|
10538
|
+
}
|
|
10539
|
+
async assertBundleTrusted(bundlePath) {
|
|
10540
|
+
const policy = this.config.macos ?? {};
|
|
10541
|
+
const trust = await verifyBundleTrust(bundlePath, policy);
|
|
10542
|
+
if (!trust.ok) {
|
|
10543
|
+
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.");
|
|
10544
|
+
}
|
|
10545
|
+
return trust;
|
|
10546
|
+
}
|
|
10547
|
+
async installMacOSPackage(downloadPath) {
|
|
10548
|
+
try {
|
|
10549
|
+
import_child_process3.execFileSync("pkgutil", ["--check-signature", downloadPath], { stdio: "pipe" });
|
|
10550
|
+
} catch (e) {
|
|
10551
|
+
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}`);
|
|
10286
10552
|
}
|
|
10553
|
+
import_child_process3.execFileSync("sudo", ["installer", "-pkg", downloadPath, "-target", "/"]);
|
|
10287
10554
|
}
|
|
10288
10555
|
async installWindowsUpdate() {
|
|
10289
10556
|
const downloadPath = this.downloadPath;
|
|
10290
10557
|
if (downloadPath.endsWith(".exe")) {
|
|
10291
|
-
|
|
10558
|
+
import_child_process3.spawn(downloadPath, ["/S", "/SILENT", "/VERYSILENT"], {
|
|
10292
10559
|
detached: true,
|
|
10293
10560
|
stdio: "ignore"
|
|
10294
10561
|
});
|
|
10295
10562
|
} else if (downloadPath.endsWith(".msi")) {
|
|
10296
|
-
|
|
10563
|
+
import_child_process3.spawn("msiexec", ["/i", downloadPath, "/quiet", "/norestart"], {
|
|
10297
10564
|
detached: true,
|
|
10298
10565
|
stdio: "ignore"
|
|
10299
10566
|
});
|
|
10300
10567
|
} else if (downloadPath.endsWith(".zip")) {
|
|
10301
|
-
const appDir =
|
|
10302
|
-
|
|
10568
|
+
const appDir = import_path3.dirname(this.config.appPath);
|
|
10569
|
+
import_child_process3.execFileSync("powershell", [
|
|
10303
10570
|
"-NoProfile",
|
|
10304
10571
|
"-NonInteractive",
|
|
10305
10572
|
"-Command",
|
|
@@ -10312,30 +10579,31 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10312
10579
|
async installLinuxUpdate() {
|
|
10313
10580
|
const downloadPath = this.downloadPath;
|
|
10314
10581
|
if (downloadPath.endsWith(".AppImage")) {
|
|
10315
|
-
|
|
10316
|
-
const { renameSync } = await import("fs");
|
|
10317
|
-
|
|
10582
|
+
import_child_process3.execFileSync("chmod", ["+x", downloadPath]);
|
|
10583
|
+
const { renameSync: renameSync2 } = await import("fs");
|
|
10584
|
+
renameSync2(downloadPath, this.config.appPath);
|
|
10318
10585
|
} else if (downloadPath.endsWith(".deb")) {
|
|
10319
|
-
|
|
10586
|
+
import_child_process3.execFileSync("sudo", ["dpkg", "-i", downloadPath]);
|
|
10320
10587
|
} else if (downloadPath.endsWith(".rpm")) {
|
|
10321
|
-
|
|
10588
|
+
import_child_process3.execFileSync("sudo", ["rpm", "-U", downloadPath]);
|
|
10322
10589
|
} else if (downloadPath.endsWith(".tar.gz")) {
|
|
10323
|
-
const appDir =
|
|
10324
|
-
|
|
10590
|
+
const appDir = import_path3.dirname(this.config.appPath);
|
|
10591
|
+
import_child_process3.execFileSync("tar", ["-xzf", downloadPath, "-C", appDir]);
|
|
10325
10592
|
}
|
|
10326
10593
|
}
|
|
10327
|
-
restartApp() {
|
|
10328
|
-
const platform = this.getPlatform();
|
|
10594
|
+
async restartApp() {
|
|
10329
10595
|
const appPath = this.config.appPath;
|
|
10330
|
-
|
|
10596
|
+
if (this.config.relaunch) {
|
|
10597
|
+
await this.config.relaunch(appPath);
|
|
10598
|
+
return;
|
|
10599
|
+
}
|
|
10600
|
+
switch (this.getPlatform()) {
|
|
10331
10601
|
case "darwin":
|
|
10332
|
-
|
|
10602
|
+
import_child_process3.spawn("open", ["-n", appPath], { detached: true, stdio: "ignore" });
|
|
10333
10603
|
break;
|
|
10334
10604
|
case "win32":
|
|
10335
|
-
import_child_process2.spawn(appPath, [], { detached: true, stdio: "ignore" });
|
|
10336
|
-
break;
|
|
10337
10605
|
case "linux":
|
|
10338
|
-
|
|
10606
|
+
import_child_process3.spawn(appPath, [], { detached: true, stdio: "ignore" });
|
|
10339
10607
|
break;
|
|
10340
10608
|
}
|
|
10341
10609
|
process.exit(0);
|
|
@@ -10366,7 +10634,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10366
10634
|
async computeFileHash(filePath) {
|
|
10367
10635
|
return new Promise((resolve, reject) => {
|
|
10368
10636
|
const hash = import_crypto3.createHash("sha256");
|
|
10369
|
-
const stream =
|
|
10637
|
+
const stream = import_fs4.createReadStream(filePath);
|
|
10370
10638
|
stream.on("data", (chunk) => hash.update(chunk));
|
|
10371
10639
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
10372
10640
|
stream.on("error", reject);
|
|
@@ -10391,7 +10659,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10391
10659
|
} catch {
|
|
10392
10660
|
return false;
|
|
10393
10661
|
}
|
|
10394
|
-
const data =
|
|
10662
|
+
const data = import_fs4.readFileSync(filePath);
|
|
10395
10663
|
if (algo === "ed25519") {
|
|
10396
10664
|
return verify(null, data, publicKey, sigBytes);
|
|
10397
10665
|
}
|
|
@@ -10409,18 +10677,32 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10409
10677
|
}
|
|
10410
10678
|
|
|
10411
10679
|
class DeltaGenerator {
|
|
10680
|
+
static supported = null;
|
|
10681
|
+
static isSupported() {
|
|
10682
|
+
if (DeltaGenerator.supported !== null)
|
|
10683
|
+
return DeltaGenerator.supported;
|
|
10684
|
+
DeltaGenerator.supported = ["bspatch", "xdelta3"].some((tool) => {
|
|
10685
|
+
try {
|
|
10686
|
+
import_child_process3.execFileSync("command", ["-v", tool], { stdio: "ignore", shell: true });
|
|
10687
|
+
return true;
|
|
10688
|
+
} catch {
|
|
10689
|
+
return false;
|
|
10690
|
+
}
|
|
10691
|
+
});
|
|
10692
|
+
return DeltaGenerator.supported;
|
|
10693
|
+
}
|
|
10412
10694
|
static async generate(oldPath, newPath, outputPath) {
|
|
10413
10695
|
try {
|
|
10414
|
-
|
|
10415
|
-
const stats =
|
|
10696
|
+
import_child_process3.execFileSync("bsdiff", [oldPath, newPath, outputPath]);
|
|
10697
|
+
const stats = import_fs4.statSync(outputPath);
|
|
10416
10698
|
const hash = await DeltaGenerator.computeHash(outputPath);
|
|
10417
10699
|
return {
|
|
10418
10700
|
size: stats.size,
|
|
10419
10701
|
sha256: hash
|
|
10420
10702
|
};
|
|
10421
10703
|
} catch {
|
|
10422
|
-
|
|
10423
|
-
const stats =
|
|
10704
|
+
import_child_process3.execFileSync("xdelta3", ["-e", "-s", oldPath, newPath, outputPath]);
|
|
10705
|
+
const stats = import_fs4.statSync(outputPath);
|
|
10424
10706
|
const hash = await DeltaGenerator.computeHash(outputPath);
|
|
10425
10707
|
return {
|
|
10426
10708
|
size: stats.size,
|
|
@@ -10430,15 +10712,15 @@ class DeltaGenerator {
|
|
|
10430
10712
|
}
|
|
10431
10713
|
static async apply(basePath, deltaPath, outputPath) {
|
|
10432
10714
|
try {
|
|
10433
|
-
|
|
10715
|
+
import_child_process3.execFileSync("bspatch", [basePath, outputPath, deltaPath]);
|
|
10434
10716
|
} catch {
|
|
10435
|
-
|
|
10717
|
+
import_child_process3.execFileSync("xdelta3", ["-d", "-s", basePath, deltaPath, outputPath]);
|
|
10436
10718
|
}
|
|
10437
10719
|
}
|
|
10438
10720
|
static async computeHash(filePath) {
|
|
10439
10721
|
return new Promise((resolve, reject) => {
|
|
10440
10722
|
const hash = import_crypto3.createHash("sha256");
|
|
10441
|
-
const stream =
|
|
10723
|
+
const stream = import_fs4.createReadStream(filePath);
|
|
10442
10724
|
stream.on("data", (chunk) => hash.update(chunk));
|
|
10443
10725
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
10444
10726
|
stream.on("error", reject);
|
|
@@ -10455,7 +10737,7 @@ function generateUpdateManifest(options) {
|
|
|
10455
10737
|
for (const [platform, info] of Object.entries(options.platforms)) {
|
|
10456
10738
|
if (!info)
|
|
10457
10739
|
continue;
|
|
10458
|
-
const bytes =
|
|
10740
|
+
const bytes = import_fs4.readFileSync(info.path);
|
|
10459
10741
|
const hash = import_crypto3.createHash("sha256").update(bytes).digest("hex");
|
|
10460
10742
|
manifest.platforms[platform] = {
|
|
10461
10743
|
url: info.url,
|
|
@@ -10465,7 +10747,7 @@ function generateUpdateManifest(options) {
|
|
|
10465
10747
|
};
|
|
10466
10748
|
const platformDeltas = options.deltas?.filter((d) => d.platform === platform) || [];
|
|
10467
10749
|
for (const delta of platformDeltas) {
|
|
10468
|
-
const deltaBytes =
|
|
10750
|
+
const deltaBytes = import_fs4.readFileSync(delta.path);
|
|
10469
10751
|
const deltaHash = import_crypto3.createHash("sha256").update(deltaBytes).digest("hex");
|
|
10470
10752
|
manifest.platforms[platform].delta.push({
|
|
10471
10753
|
fromVersion: delta.fromVersion,
|
|
@@ -10534,7 +10816,7 @@ ${update.releaseNotes}`);
|
|
|
10534
10816
|
releaseDate: new Date().toISOString(),
|
|
10535
10817
|
platforms: {}
|
|
10536
10818
|
};
|
|
10537
|
-
|
|
10819
|
+
import_fs4.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
|
|
10538
10820
|
console.log(`Manifest generated: ${outputPath}`);
|
|
10539
10821
|
console.log("Edit the file to add platform-specific update URLs");
|
|
10540
10822
|
break;
|
|
@@ -10560,7 +10842,7 @@ Examples:
|
|
|
10560
10842
|
var updater_default = AutoUpdater;
|
|
10561
10843
|
|
|
10562
10844
|
// src/index.ts
|
|
10563
|
-
var
|
|
10845
|
+
var execFileAsync2 = import_node_util.promisify(import_node_child_process.execFile);
|
|
10564
10846
|
var versionMismatchWarned = false;
|
|
10565
10847
|
function getSdkVersion() {
|
|
10566
10848
|
try {
|
|
@@ -10574,7 +10856,7 @@ async function probeBinaryVersion(craftPath) {
|
|
|
10574
10856
|
if (versionMismatchWarned)
|
|
10575
10857
|
return;
|
|
10576
10858
|
try {
|
|
10577
|
-
const { stdout } = await
|
|
10859
|
+
const { stdout } = await execFileAsync2(craftPath, ["--version"], { timeout: 2000 });
|
|
10578
10860
|
const nativeVersion = parseCraftVersionOutput(stdout);
|
|
10579
10861
|
const sdkVersion = getSdkVersion();
|
|
10580
10862
|
if (sdkVersion !== "unknown" && nativeVersion && nativeVersion !== sdkVersion) {
|
|
@@ -10749,7 +11031,9 @@ class CraftApp {
|
|
|
10749
11031
|
args.push("--keep-running");
|
|
10750
11032
|
else if (window3?.keepRunning === false)
|
|
10751
11033
|
args.push("--quit-on-close");
|
|
10752
|
-
if (window3?.
|
|
11034
|
+
if (window3?.webWindowMaterial) {
|
|
11035
|
+
args.push("--web-window-material");
|
|
11036
|
+
} else if (window3?.webSidebarMaterial) {
|
|
10753
11037
|
args.push("--web-sidebar-material");
|
|
10754
11038
|
if (window3?.webSidebarWidth)
|
|
10755
11039
|
args.push("--web-sidebar-width", String(window3.webSidebarWidth));
|