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.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,
|
|
@@ -2603,9 +2612,12 @@ class WindowManager {
|
|
|
2603
2612
|
return this._windows.get(id);
|
|
2604
2613
|
}
|
|
2605
2614
|
async create(options = {}) {
|
|
2606
|
-
const id = `window_${++this._idCounter}_${Date.now()}`;
|
|
2615
|
+
const id = options.id || `window_${++this._idCounter}_${Date.now()}`;
|
|
2616
|
+
const existing = this._windows.get(id);
|
|
2607
2617
|
const bridge = getBridge();
|
|
2608
|
-
await bridge.request("window.create", {
|
|
2618
|
+
await bridge.request("window.create", { ...options, id });
|
|
2619
|
+
if (existing)
|
|
2620
|
+
return existing;
|
|
2609
2621
|
const win = new Window(id);
|
|
2610
2622
|
this._windows.set(id, win);
|
|
2611
2623
|
return win;
|
|
@@ -9983,24 +9995,236 @@ var commonOptimizations = {
|
|
|
9983
9995
|
// src/updater/index.ts
|
|
9984
9996
|
var exports_updater = {};
|
|
9985
9997
|
__export(exports_updater, {
|
|
9998
|
+
verifyBundleTrust: () => verifyBundleTrust,
|
|
9986
9999
|
updaterCommand: () => updaterCommand,
|
|
10000
|
+
swapBundle: () => swapBundle,
|
|
10001
|
+
readBundleIdentity: () => readBundleIdentity,
|
|
10002
|
+
isMacOS: () => isMacOS,
|
|
9987
10003
|
generateUpdateManifest: () => generateUpdateManifest,
|
|
10004
|
+
extractBundleFromZip: () => extractBundleFromZip,
|
|
10005
|
+
extractBundleFromDmg: () => extractBundleFromDmg,
|
|
10006
|
+
extractBundle: () => extractBundle,
|
|
10007
|
+
dittoBundle: () => dittoBundle,
|
|
9988
10008
|
default: () => updater_default,
|
|
10009
|
+
clearQuarantine: () => clearQuarantine,
|
|
10010
|
+
canReplaceBundle: () => canReplaceBundle,
|
|
9989
10011
|
DeltaGenerator: () => DeltaGenerator,
|
|
9990
10012
|
AutoUpdater: () => AutoUpdater
|
|
9991
10013
|
});
|
|
9992
|
-
var
|
|
9993
|
-
var
|
|
9994
|
-
var
|
|
10014
|
+
var import_fs4 = require("fs");
|
|
10015
|
+
var import_path3 = require("path");
|
|
10016
|
+
var import_os3 = require("os");
|
|
10017
|
+
var import_child_process3 = require("child_process");
|
|
9995
10018
|
var import_crypto3 = require("crypto");
|
|
9996
10019
|
var import_events3 = require("events");
|
|
9997
|
-
|
|
9998
|
-
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10020
|
+
|
|
10021
|
+
// src/updater/macos-bundle.ts
|
|
10022
|
+
var import_child_process2 = require("child_process");
|
|
10023
|
+
var import_fs3 = require("fs");
|
|
10024
|
+
var import_os2 = require("os");
|
|
10025
|
+
var import_path2 = require("path");
|
|
10026
|
+
var import_util = require("util");
|
|
10027
|
+
var execFileAsync = import_util.promisify(import_child_process2.execFile);
|
|
10028
|
+
function isMacOS() {
|
|
10029
|
+
return process.platform === "darwin";
|
|
10030
|
+
}
|
|
10031
|
+
async function readBundleIdentity(appPath) {
|
|
10032
|
+
const empty = { identifier: null, teamId: null, authority: null };
|
|
10033
|
+
if (!isMacOS())
|
|
10034
|
+
return empty;
|
|
10035
|
+
let report;
|
|
10036
|
+
try {
|
|
10037
|
+
const { stderr, stdout } = await execFileAsync("codesign", ["-dv", "--verbose=4", appPath]);
|
|
10038
|
+
report = `${stderr}
|
|
10039
|
+
${stdout}`;
|
|
10040
|
+
} catch (error) {
|
|
10041
|
+
const e = error;
|
|
10042
|
+
report = `${e.stderr ?? ""}
|
|
10043
|
+
${e.stdout ?? ""}`;
|
|
10044
|
+
if (!report.trim())
|
|
10045
|
+
return empty;
|
|
10046
|
+
}
|
|
10047
|
+
const field = (name) => {
|
|
10048
|
+
const match = report.match(new RegExp(`^${name}=(.+)$`, "m"));
|
|
10049
|
+
if (!match)
|
|
10050
|
+
return null;
|
|
10051
|
+
const value = match[1].trim();
|
|
10052
|
+
return value === "not set" || value.length === 0 ? null : value;
|
|
10053
|
+
};
|
|
10054
|
+
return {
|
|
10055
|
+
identifier: field("Identifier"),
|
|
10056
|
+
teamId: field("TeamIdentifier"),
|
|
10057
|
+
authority: field("Authority")
|
|
10058
|
+
};
|
|
10059
|
+
}
|
|
10060
|
+
async function verifyBundleTrust(appPath, policy = {}) {
|
|
10061
|
+
const identity = await readBundleIdentity(appPath);
|
|
10062
|
+
const requireNotarized = policy.requireNotarized ?? true;
|
|
10063
|
+
if (!import_fs3.existsSync(appPath)) {
|
|
10064
|
+
return { ok: false, identity, gatekeeperSource: null, reason: "unreadable", detail: `No such bundle: ${appPath}` };
|
|
10065
|
+
}
|
|
10066
|
+
try {
|
|
10067
|
+
await execFileAsync("codesign", ["--verify", "--deep", "--strict", "--verbose=2", appPath]);
|
|
10068
|
+
} catch (error) {
|
|
10069
|
+
return {
|
|
10070
|
+
ok: false,
|
|
10071
|
+
identity,
|
|
10072
|
+
gatekeeperSource: null,
|
|
10073
|
+
reason: "codesign-invalid",
|
|
10074
|
+
detail: describe(error)
|
|
10075
|
+
};
|
|
10076
|
+
}
|
|
10077
|
+
let gatekeeperSource = null;
|
|
10078
|
+
if (requireNotarized) {
|
|
10079
|
+
try {
|
|
10080
|
+
const { stderr, stdout } = await execFileAsync("spctl", ["-a", "-t", "exec", "-vv", appPath]);
|
|
10081
|
+
const report = `${stderr}
|
|
10082
|
+
${stdout}`;
|
|
10083
|
+
gatekeeperSource = report.match(/^source=(.+)$/m)?.[1]?.trim() ?? null;
|
|
10084
|
+
} catch (error) {
|
|
10085
|
+
return {
|
|
10086
|
+
ok: false,
|
|
10087
|
+
identity,
|
|
10088
|
+
gatekeeperSource: null,
|
|
10089
|
+
reason: "gatekeeper-rejected",
|
|
10090
|
+
detail: describe(error)
|
|
10091
|
+
};
|
|
10092
|
+
}
|
|
10093
|
+
}
|
|
10094
|
+
if (policy.teamId && identity.teamId !== policy.teamId) {
|
|
10095
|
+
return {
|
|
10096
|
+
ok: false,
|
|
10097
|
+
identity,
|
|
10098
|
+
gatekeeperSource,
|
|
10099
|
+
reason: "team-mismatch",
|
|
10100
|
+
detail: `Expected Team ID ${policy.teamId}, bundle is signed by ${identity.teamId ?? "nobody"}`
|
|
10101
|
+
};
|
|
10102
|
+
}
|
|
10103
|
+
return { ok: true, identity, gatekeeperSource };
|
|
10104
|
+
}
|
|
10105
|
+
async function clearQuarantine(appPath) {
|
|
10106
|
+
if (!isMacOS())
|
|
10107
|
+
return;
|
|
10108
|
+
try {
|
|
10109
|
+
await execFileAsync("xattr", ["-d", "-r", "com.apple.quarantine", appPath]);
|
|
10110
|
+
} catch {}
|
|
10111
|
+
}
|
|
10112
|
+
async function dittoBundle(source, destination) {
|
|
10113
|
+
await execFileAsync("ditto", [source, destination]);
|
|
10114
|
+
}
|
|
10115
|
+
function findTopLevelApp(root) {
|
|
10116
|
+
const out = import_child_process2.execFileSync("find", [root, "-maxdepth", "2", "-name", "*.app", "-type", "d", "-print0"]);
|
|
10117
|
+
const parts = out.toString("utf-8").split("\x00").filter((s) => s.length > 0);
|
|
10118
|
+
parts.sort((a, b) => a.split("/").length - b.split("/").length);
|
|
10119
|
+
return parts[0] ?? null;
|
|
10120
|
+
}
|
|
10121
|
+
async function extractBundleFromDmg(dmgPath, stagingDir) {
|
|
10122
|
+
const staging = stagingDir ?? import_fs3.mkdtempSync(import_path2.join(import_os2.tmpdir(), "craft-update-"));
|
|
10123
|
+
const mountPoint = import_fs3.mkdtempSync(import_path2.join(import_os2.tmpdir(), "craft-mount-"));
|
|
10124
|
+
let mounted = false;
|
|
10125
|
+
try {
|
|
10126
|
+
await execFileAsync("hdiutil", ["attach", dmgPath, "-nobrowse", "-readonly", "-mountpoint", mountPoint]);
|
|
10127
|
+
mounted = true;
|
|
10128
|
+
const source = findTopLevelApp(mountPoint);
|
|
10129
|
+
if (!source)
|
|
10130
|
+
throw new Error(`No .app found in disk image: ${dmgPath}`);
|
|
10131
|
+
const appPath = import_path2.join(staging, import_path2.basename(source));
|
|
10132
|
+
await dittoBundle(source, appPath);
|
|
10133
|
+
return { appPath, stagingDir: staging };
|
|
10134
|
+
} finally {
|
|
10135
|
+
if (mounted) {
|
|
10136
|
+
try {
|
|
10137
|
+
await execFileAsync("hdiutil", ["detach", mountPoint, "-force"]);
|
|
10138
|
+
} catch {}
|
|
10139
|
+
}
|
|
10140
|
+
try {
|
|
10141
|
+
import_fs3.rmSync(mountPoint, { recursive: true, force: true });
|
|
10142
|
+
} catch {}
|
|
10143
|
+
}
|
|
10144
|
+
}
|
|
10145
|
+
async function extractBundleFromZip(zipPath, stagingDir) {
|
|
10146
|
+
const staging = stagingDir ?? import_fs3.mkdtempSync(import_path2.join(import_os2.tmpdir(), "craft-update-"));
|
|
10147
|
+
const unpacked = import_path2.join(staging, "payload");
|
|
10148
|
+
await execFileAsync("ditto", ["-x", "-k", zipPath, unpacked]);
|
|
10149
|
+
const source = findTopLevelApp(unpacked);
|
|
10150
|
+
if (!source)
|
|
10151
|
+
throw new Error(`No .app found in archive: ${zipPath}`);
|
|
10152
|
+
return { appPath: source, stagingDir: staging };
|
|
10153
|
+
}
|
|
10154
|
+
async function extractBundle(archivePath, stagingDir) {
|
|
10155
|
+
if (archivePath.endsWith(".dmg"))
|
|
10156
|
+
return extractBundleFromDmg(archivePath, stagingDir);
|
|
10157
|
+
if (archivePath.endsWith(".zip"))
|
|
10158
|
+
return extractBundleFromZip(archivePath, stagingDir);
|
|
10159
|
+
throw new Error(`Cannot extract an app bundle from ${archivePath}`);
|
|
10160
|
+
}
|
|
10161
|
+
async function swapBundle(stagedPath, installedPath) {
|
|
10162
|
+
if (!import_fs3.existsSync(stagedPath))
|
|
10163
|
+
throw new Error(`Staged bundle is missing: ${stagedPath}`);
|
|
10164
|
+
const parent = import_path2.dirname(installedPath);
|
|
10165
|
+
const stamp = `${Date.now()}.${process.pid}`;
|
|
10166
|
+
const incoming = import_path2.join(parent, `.${import_path2.basename(installedPath)}.${stamp}.incoming`);
|
|
10167
|
+
const retired = import_path2.join(parent, `.${import_path2.basename(installedPath)}.${stamp}.retired`);
|
|
10168
|
+
try {
|
|
10169
|
+
import_fs3.renameSync(stagedPath, incoming);
|
|
10170
|
+
} catch {
|
|
10171
|
+
await dittoBundle(stagedPath, incoming);
|
|
10172
|
+
}
|
|
10173
|
+
const hadPrevious = import_fs3.existsSync(installedPath);
|
|
10174
|
+
try {
|
|
10175
|
+
if (hadPrevious)
|
|
10176
|
+
import_fs3.renameSync(installedPath, retired);
|
|
10177
|
+
try {
|
|
10178
|
+
import_fs3.renameSync(incoming, installedPath);
|
|
10179
|
+
} catch (error) {
|
|
10180
|
+
if (hadPrevious) {
|
|
10181
|
+
try {
|
|
10182
|
+
import_fs3.renameSync(retired, installedPath);
|
|
10183
|
+
} catch {}
|
|
10184
|
+
}
|
|
10185
|
+
throw error;
|
|
10186
|
+
}
|
|
10187
|
+
} catch (error) {
|
|
10188
|
+
try {
|
|
10189
|
+
import_fs3.rmSync(incoming, { recursive: true, force: true });
|
|
10190
|
+
} catch {}
|
|
10191
|
+
throw error;
|
|
10192
|
+
}
|
|
10193
|
+
if (hadPrevious) {
|
|
10194
|
+
try {
|
|
10195
|
+
import_fs3.rmSync(retired, { recursive: true, force: true });
|
|
10196
|
+
return { previousPath: null };
|
|
10197
|
+
} catch {
|
|
10198
|
+
return { previousPath: retired };
|
|
10199
|
+
}
|
|
10200
|
+
}
|
|
10201
|
+
return { previousPath: null };
|
|
10202
|
+
}
|
|
10203
|
+
function canReplaceBundle(installedPath) {
|
|
10204
|
+
try {
|
|
10205
|
+
const parent = import_path2.dirname(installedPath);
|
|
10206
|
+
import_fs3.accessSync(parent, import_fs3.constants.W_OK);
|
|
10207
|
+
if (import_fs3.existsSync(installedPath))
|
|
10208
|
+
import_fs3.statSync(installedPath);
|
|
10209
|
+
return true;
|
|
10210
|
+
} catch {
|
|
10211
|
+
return false;
|
|
10212
|
+
}
|
|
10213
|
+
}
|
|
10214
|
+
function describe(error) {
|
|
10215
|
+
if (error && typeof error === "object") {
|
|
10216
|
+
const e = error;
|
|
10217
|
+
const stderr = e.stderr?.trim();
|
|
10218
|
+
if (stderr)
|
|
10219
|
+
return stderr;
|
|
10220
|
+
if (e.message)
|
|
10221
|
+
return e.message;
|
|
10222
|
+
}
|
|
10223
|
+
return String(error);
|
|
10002
10224
|
}
|
|
10003
10225
|
|
|
10226
|
+
// src/updater/index.ts
|
|
10227
|
+
var PROGRESS_INTERVAL_MS = 100;
|
|
10004
10228
|
class AutoUpdater extends import_events3.EventEmitter {
|
|
10005
10229
|
config;
|
|
10006
10230
|
updateInfo = null;
|
|
@@ -10009,12 +10233,22 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10009
10233
|
cachedEtag = null;
|
|
10010
10234
|
cachedLastModified = null;
|
|
10011
10235
|
cachedManifest = null;
|
|
10236
|
+
lastError = null;
|
|
10012
10237
|
emitError(error) {
|
|
10238
|
+
this.lastError = error instanceof Error ? error : new Error(String(error));
|
|
10013
10239
|
if (this.listenerCount("error") > 0)
|
|
10014
10240
|
this.emit("error", error);
|
|
10015
10241
|
else
|
|
10016
10242
|
console.error("[Updater]", error);
|
|
10017
10243
|
}
|
|
10244
|
+
downloadDir() {
|
|
10245
|
+
if (this.config.downloadDir)
|
|
10246
|
+
return this.config.downloadDir;
|
|
10247
|
+
return import_path3.join(import_os3.tmpdir(), "craft-updates", import_path3.basename(this.config.appPath) || "app");
|
|
10248
|
+
}
|
|
10249
|
+
getLastError() {
|
|
10250
|
+
return this.lastError;
|
|
10251
|
+
}
|
|
10018
10252
|
constructor(config) {
|
|
10019
10253
|
super();
|
|
10020
10254
|
const url = config.updateUrl;
|
|
@@ -10048,6 +10282,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10048
10282
|
}
|
|
10049
10283
|
async checkForUpdates() {
|
|
10050
10284
|
this.emit("checking-for-update");
|
|
10285
|
+
this.lastError = null;
|
|
10051
10286
|
try {
|
|
10052
10287
|
const platform = this.getPlatform();
|
|
10053
10288
|
const url = `${this.config.updateUrl}?v=${this.config.currentVersion}&channel=${this.config.channel}&platform=${platform}`;
|
|
@@ -10096,20 +10331,21 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10096
10331
|
if (!this.updateInfo) {
|
|
10097
10332
|
throw new Error("No update available");
|
|
10098
10333
|
}
|
|
10334
|
+
this.lastError = null;
|
|
10099
10335
|
const platform = this.getPlatform();
|
|
10100
10336
|
const platformUpdate = this.updateInfo.platforms[platform];
|
|
10101
10337
|
if (!platformUpdate) {
|
|
10102
10338
|
throw new Error(`No update available for platform: ${platform}`);
|
|
10103
10339
|
}
|
|
10104
|
-
const deltaUpdate = platformUpdate.delta?.find((d) => d.fromVersion === this.config.currentVersion);
|
|
10340
|
+
const deltaUpdate = DeltaGenerator.isSupported() ? platformUpdate.delta?.find((d) => d.fromVersion === this.config.currentVersion) : undefined;
|
|
10105
10341
|
const _updateSource = deltaUpdate || platformUpdate;
|
|
10106
10342
|
const url = deltaUpdate?.url || platformUpdate.url;
|
|
10107
10343
|
const expectedSize = deltaUpdate?.size || platformUpdate.size;
|
|
10108
10344
|
const expectedHash = deltaUpdate?.sha256 || platformUpdate.sha256;
|
|
10109
|
-
const downloadDir =
|
|
10110
|
-
|
|
10111
|
-
const fileName =
|
|
10112
|
-
this.downloadPath =
|
|
10345
|
+
const downloadDir = this.downloadDir();
|
|
10346
|
+
import_fs4.mkdirSync(downloadDir, { recursive: true });
|
|
10347
|
+
const fileName = import_path3.basename(url);
|
|
10348
|
+
this.downloadPath = import_path3.join(downloadDir, fileName);
|
|
10113
10349
|
try {
|
|
10114
10350
|
const response = await fetch(url);
|
|
10115
10351
|
if (!response.ok) {
|
|
@@ -10119,63 +10355,83 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10119
10355
|
const total = contentLength || expectedSize;
|
|
10120
10356
|
let downloaded = 0;
|
|
10121
10357
|
const startTime = Date.now();
|
|
10122
|
-
const fileStream =
|
|
10358
|
+
const fileStream = import_fs4.createWriteStream(this.downloadPath);
|
|
10123
10359
|
const reader = response.body?.getReader();
|
|
10124
10360
|
if (!reader) {
|
|
10125
10361
|
throw new Error("Failed to get response reader");
|
|
10126
10362
|
}
|
|
10363
|
+
const digest = import_crypto3.createHash("sha256");
|
|
10364
|
+
let lastProgressAt = 0;
|
|
10127
10365
|
while (true) {
|
|
10128
10366
|
const { done, value } = await reader.read();
|
|
10129
10367
|
if (done)
|
|
10130
10368
|
break;
|
|
10131
|
-
|
|
10369
|
+
digest.update(value);
|
|
10370
|
+
if (!fileStream.write(value)) {
|
|
10371
|
+
await new Promise((resolve, reject) => {
|
|
10372
|
+
const onDrain = () => {
|
|
10373
|
+
fileStream.off("error", onError);
|
|
10374
|
+
resolve();
|
|
10375
|
+
};
|
|
10376
|
+
const onError = (error) => {
|
|
10377
|
+
fileStream.off("drain", onDrain);
|
|
10378
|
+
reject(error);
|
|
10379
|
+
};
|
|
10380
|
+
fileStream.once("drain", onDrain);
|
|
10381
|
+
fileStream.once("error", onError);
|
|
10382
|
+
});
|
|
10383
|
+
}
|
|
10132
10384
|
downloaded += value.length;
|
|
10133
|
-
const
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
10140
|
-
|
|
10141
|
-
|
|
10142
|
-
|
|
10385
|
+
const now = Date.now();
|
|
10386
|
+
if (now - lastProgressAt >= PROGRESS_INTERVAL_MS || downloaded === total) {
|
|
10387
|
+
lastProgressAt = now;
|
|
10388
|
+
const elapsed = (now - startTime) / 1000;
|
|
10389
|
+
const speed = elapsed > 0 ? downloaded / elapsed : 0;
|
|
10390
|
+
const progress = {
|
|
10391
|
+
phase: "downloading",
|
|
10392
|
+
percent: total > 0 ? Math.round(downloaded / total * 100) : 0,
|
|
10393
|
+
bytesDownloaded: downloaded,
|
|
10394
|
+
bytesTotal: total,
|
|
10395
|
+
speed
|
|
10396
|
+
};
|
|
10397
|
+
this.emit("download-progress", progress);
|
|
10398
|
+
}
|
|
10143
10399
|
}
|
|
10144
10400
|
fileStream.end();
|
|
10145
10401
|
await new Promise((resolve, reject) => {
|
|
10146
10402
|
fileStream.once("finish", resolve);
|
|
10147
10403
|
fileStream.once("error", reject);
|
|
10148
10404
|
});
|
|
10149
|
-
const hash =
|
|
10405
|
+
const hash = digest.digest("hex");
|
|
10150
10406
|
if (hash !== expectedHash) {
|
|
10151
|
-
|
|
10407
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10152
10408
|
throw new Error("Download verification failed: hash mismatch");
|
|
10153
10409
|
}
|
|
10154
10410
|
if (deltaUpdate) {
|
|
10155
|
-
if (!
|
|
10411
|
+
if (!import_fs4.statSync(this.config.appPath).isFile()) {
|
|
10156
10412
|
throw new Error("Delta updates require appPath to point to the installed bundle file");
|
|
10157
10413
|
}
|
|
10158
10414
|
const patchPath = this.downloadPath;
|
|
10159
|
-
const reconstructedPath =
|
|
10415
|
+
const reconstructedPath = import_path3.join(downloadDir, `reconstructed-${import_path3.basename(platformUpdate.url)}`);
|
|
10160
10416
|
await DeltaGenerator.apply(this.config.appPath, patchPath, reconstructedPath);
|
|
10161
10417
|
const reconstructedHash = await this.computeFileHash(reconstructedPath);
|
|
10162
10418
|
if (reconstructedHash !== platformUpdate.sha256) {
|
|
10163
10419
|
try {
|
|
10164
|
-
|
|
10420
|
+
import_fs4.unlinkSync(reconstructedPath);
|
|
10165
10421
|
} catch {}
|
|
10166
10422
|
throw new Error("Delta reconstruction failed: full bundle hash mismatch");
|
|
10167
10423
|
}
|
|
10168
|
-
|
|
10424
|
+
import_fs4.unlinkSync(patchPath);
|
|
10169
10425
|
this.downloadPath = reconstructedPath;
|
|
10170
10426
|
}
|
|
10171
10427
|
if (this.config.publicKeyPem && !platformUpdate.signature) {
|
|
10172
|
-
|
|
10428
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10173
10429
|
throw new Error("Updater: publicKeyPem is configured but the manifest entry has no signature. " + "Refusing to install unsigned update.");
|
|
10174
10430
|
}
|
|
10175
10431
|
if (platformUpdate.signature) {
|
|
10176
10432
|
const valid = await this.verifySignature(this.downloadPath, platformUpdate.signature);
|
|
10177
10433
|
if (!valid) {
|
|
10178
|
-
|
|
10434
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10179
10435
|
throw new Error("Download verification failed: invalid signature");
|
|
10180
10436
|
}
|
|
10181
10437
|
}
|
|
@@ -10194,7 +10450,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10194
10450
|
}
|
|
10195
10451
|
}
|
|
10196
10452
|
async installUpdate(restartAfter = true) {
|
|
10197
|
-
if (!this.downloadPath || !
|
|
10453
|
+
if (!this.downloadPath || !import_fs4.existsSync(this.downloadPath)) {
|
|
10198
10454
|
throw new Error("No update downloaded");
|
|
10199
10455
|
}
|
|
10200
10456
|
if (this.updateInfo) {
|
|
@@ -10204,7 +10460,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10204
10460
|
const hash = await this.computeFileHash(this.downloadPath);
|
|
10205
10461
|
if (hash !== platformUpdate.sha256) {
|
|
10206
10462
|
try {
|
|
10207
|
-
|
|
10463
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10208
10464
|
} catch {}
|
|
10209
10465
|
throw new Error(`Updater.installUpdate: pre-install hash mismatch on ${this.downloadPath}. ` + "The downloaded bundle changed between download and install — refusing to run installer.");
|
|
10210
10466
|
}
|
|
@@ -10215,7 +10471,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10215
10471
|
const valid = await this.verifySignature(this.downloadPath, platformUpdate.signature);
|
|
10216
10472
|
if (!valid) {
|
|
10217
10473
|
try {
|
|
10218
|
-
|
|
10474
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10219
10475
|
} catch {}
|
|
10220
10476
|
throw new Error("Updater.installUpdate: invalid signature on downloaded bundle");
|
|
10221
10477
|
}
|
|
@@ -10238,14 +10494,17 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10238
10494
|
await this.installLinuxUpdate();
|
|
10239
10495
|
break;
|
|
10240
10496
|
}
|
|
10241
|
-
if (this.downloadPath &&
|
|
10242
|
-
|
|
10497
|
+
if (this.downloadPath && import_fs4.existsSync(this.downloadPath)) {
|
|
10498
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10243
10499
|
}
|
|
10500
|
+
try {
|
|
10501
|
+
import_fs4.rmdirSync(this.downloadDir());
|
|
10502
|
+
} catch {}
|
|
10244
10503
|
progress.phase = "done";
|
|
10245
10504
|
progress.percent = 100;
|
|
10246
10505
|
this.emit("download-progress", progress);
|
|
10247
10506
|
if (restartAfter) {
|
|
10248
|
-
this.restartApp();
|
|
10507
|
+
await this.restartApp();
|
|
10249
10508
|
}
|
|
10250
10509
|
} catch (error) {
|
|
10251
10510
|
progress.phase = "error";
|
|
@@ -10256,54 +10515,61 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10256
10515
|
async installMacOSUpdate() {
|
|
10257
10516
|
const downloadPath = this.downloadPath;
|
|
10258
10517
|
const appPath = this.config.appPath;
|
|
10259
|
-
if (downloadPath.endsWith(".
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
rmSync2(appPath, { recursive: true, force: true });
|
|
10279
|
-
cpSync2(dmgApp, appPath, { recursive: true });
|
|
10280
|
-
}
|
|
10281
|
-
import_child_process2.execFileSync("hdiutil", ["detach", mountPoint]);
|
|
10282
|
-
}
|
|
10283
|
-
} else if (downloadPath.endsWith(".pkg")) {
|
|
10518
|
+
if (downloadPath.endsWith(".pkg")) {
|
|
10519
|
+
await this.installMacOSPackage(downloadPath);
|
|
10520
|
+
return;
|
|
10521
|
+
}
|
|
10522
|
+
if (!canReplaceBundle(appPath)) {
|
|
10523
|
+
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.");
|
|
10524
|
+
}
|
|
10525
|
+
const staged = await extractBundle(downloadPath);
|
|
10526
|
+
try {
|
|
10527
|
+
const trust = await this.assertBundleTrusted(staged.appPath);
|
|
10528
|
+
await clearQuarantine(staged.appPath);
|
|
10529
|
+
const { previousPath } = await swapBundle(staged.appPath, appPath);
|
|
10530
|
+
this.emit("update-installed", {
|
|
10531
|
+
path: appPath,
|
|
10532
|
+
version: this.updateInfo?.version,
|
|
10533
|
+
identity: trust.identity,
|
|
10534
|
+
leftoverPath: previousPath
|
|
10535
|
+
});
|
|
10536
|
+
} finally {
|
|
10284
10537
|
try {
|
|
10285
|
-
|
|
10286
|
-
} catch
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10538
|
+
import_fs4.rmSync(staged.stagingDir, { recursive: true, force: true });
|
|
10539
|
+
} catch {}
|
|
10540
|
+
}
|
|
10541
|
+
}
|
|
10542
|
+
async assertBundleTrusted(bundlePath) {
|
|
10543
|
+
const policy = this.config.macos ?? {};
|
|
10544
|
+
const trust = await verifyBundleTrust(bundlePath, policy);
|
|
10545
|
+
if (!trust.ok) {
|
|
10546
|
+
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.");
|
|
10547
|
+
}
|
|
10548
|
+
return trust;
|
|
10549
|
+
}
|
|
10550
|
+
async installMacOSPackage(downloadPath) {
|
|
10551
|
+
try {
|
|
10552
|
+
import_child_process3.execFileSync("pkgutil", ["--check-signature", downloadPath], { stdio: "pipe" });
|
|
10553
|
+
} catch (e) {
|
|
10554
|
+
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}`);
|
|
10290
10555
|
}
|
|
10556
|
+
import_child_process3.execFileSync("sudo", ["installer", "-pkg", downloadPath, "-target", "/"]);
|
|
10291
10557
|
}
|
|
10292
10558
|
async installWindowsUpdate() {
|
|
10293
10559
|
const downloadPath = this.downloadPath;
|
|
10294
10560
|
if (downloadPath.endsWith(".exe")) {
|
|
10295
|
-
|
|
10561
|
+
import_child_process3.spawn(downloadPath, ["/S", "/SILENT", "/VERYSILENT"], {
|
|
10296
10562
|
detached: true,
|
|
10297
10563
|
stdio: "ignore"
|
|
10298
10564
|
});
|
|
10299
10565
|
} else if (downloadPath.endsWith(".msi")) {
|
|
10300
|
-
|
|
10566
|
+
import_child_process3.spawn("msiexec", ["/i", downloadPath, "/quiet", "/norestart"], {
|
|
10301
10567
|
detached: true,
|
|
10302
10568
|
stdio: "ignore"
|
|
10303
10569
|
});
|
|
10304
10570
|
} else if (downloadPath.endsWith(".zip")) {
|
|
10305
|
-
const appDir =
|
|
10306
|
-
|
|
10571
|
+
const appDir = import_path3.dirname(this.config.appPath);
|
|
10572
|
+
import_child_process3.execFileSync("powershell", [
|
|
10307
10573
|
"-NoProfile",
|
|
10308
10574
|
"-NonInteractive",
|
|
10309
10575
|
"-Command",
|
|
@@ -10316,30 +10582,31 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10316
10582
|
async installLinuxUpdate() {
|
|
10317
10583
|
const downloadPath = this.downloadPath;
|
|
10318
10584
|
if (downloadPath.endsWith(".AppImage")) {
|
|
10319
|
-
|
|
10320
|
-
const { renameSync } = await import("fs");
|
|
10321
|
-
|
|
10585
|
+
import_child_process3.execFileSync("chmod", ["+x", downloadPath]);
|
|
10586
|
+
const { renameSync: renameSync2 } = await import("fs");
|
|
10587
|
+
renameSync2(downloadPath, this.config.appPath);
|
|
10322
10588
|
} else if (downloadPath.endsWith(".deb")) {
|
|
10323
|
-
|
|
10589
|
+
import_child_process3.execFileSync("sudo", ["dpkg", "-i", downloadPath]);
|
|
10324
10590
|
} else if (downloadPath.endsWith(".rpm")) {
|
|
10325
|
-
|
|
10591
|
+
import_child_process3.execFileSync("sudo", ["rpm", "-U", downloadPath]);
|
|
10326
10592
|
} else if (downloadPath.endsWith(".tar.gz")) {
|
|
10327
|
-
const appDir =
|
|
10328
|
-
|
|
10593
|
+
const appDir = import_path3.dirname(this.config.appPath);
|
|
10594
|
+
import_child_process3.execFileSync("tar", ["-xzf", downloadPath, "-C", appDir]);
|
|
10329
10595
|
}
|
|
10330
10596
|
}
|
|
10331
|
-
restartApp() {
|
|
10332
|
-
const platform = this.getPlatform();
|
|
10597
|
+
async restartApp() {
|
|
10333
10598
|
const appPath = this.config.appPath;
|
|
10334
|
-
|
|
10599
|
+
if (this.config.relaunch) {
|
|
10600
|
+
await this.config.relaunch(appPath);
|
|
10601
|
+
return;
|
|
10602
|
+
}
|
|
10603
|
+
switch (this.getPlatform()) {
|
|
10335
10604
|
case "darwin":
|
|
10336
|
-
|
|
10605
|
+
import_child_process3.spawn("open", ["-n", appPath], { detached: true, stdio: "ignore" });
|
|
10337
10606
|
break;
|
|
10338
10607
|
case "win32":
|
|
10339
|
-
import_child_process2.spawn(appPath, [], { detached: true, stdio: "ignore" });
|
|
10340
|
-
break;
|
|
10341
10608
|
case "linux":
|
|
10342
|
-
|
|
10609
|
+
import_child_process3.spawn(appPath, [], { detached: true, stdio: "ignore" });
|
|
10343
10610
|
break;
|
|
10344
10611
|
}
|
|
10345
10612
|
process.exit(0);
|
|
@@ -10370,7 +10637,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10370
10637
|
async computeFileHash(filePath) {
|
|
10371
10638
|
return new Promise((resolve, reject) => {
|
|
10372
10639
|
const hash = import_crypto3.createHash("sha256");
|
|
10373
|
-
const stream =
|
|
10640
|
+
const stream = import_fs4.createReadStream(filePath);
|
|
10374
10641
|
stream.on("data", (chunk) => hash.update(chunk));
|
|
10375
10642
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
10376
10643
|
stream.on("error", reject);
|
|
@@ -10395,7 +10662,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10395
10662
|
} catch {
|
|
10396
10663
|
return false;
|
|
10397
10664
|
}
|
|
10398
|
-
const data =
|
|
10665
|
+
const data = import_fs4.readFileSync(filePath);
|
|
10399
10666
|
if (algo === "ed25519") {
|
|
10400
10667
|
return verify(null, data, publicKey, sigBytes);
|
|
10401
10668
|
}
|
|
@@ -10413,18 +10680,32 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10413
10680
|
}
|
|
10414
10681
|
|
|
10415
10682
|
class DeltaGenerator {
|
|
10683
|
+
static supported = null;
|
|
10684
|
+
static isSupported() {
|
|
10685
|
+
if (DeltaGenerator.supported !== null)
|
|
10686
|
+
return DeltaGenerator.supported;
|
|
10687
|
+
DeltaGenerator.supported = ["bspatch", "xdelta3"].some((tool) => {
|
|
10688
|
+
try {
|
|
10689
|
+
import_child_process3.execFileSync("command", ["-v", tool], { stdio: "ignore", shell: true });
|
|
10690
|
+
return true;
|
|
10691
|
+
} catch {
|
|
10692
|
+
return false;
|
|
10693
|
+
}
|
|
10694
|
+
});
|
|
10695
|
+
return DeltaGenerator.supported;
|
|
10696
|
+
}
|
|
10416
10697
|
static async generate(oldPath, newPath, outputPath) {
|
|
10417
10698
|
try {
|
|
10418
|
-
|
|
10419
|
-
const stats =
|
|
10699
|
+
import_child_process3.execFileSync("bsdiff", [oldPath, newPath, outputPath]);
|
|
10700
|
+
const stats = import_fs4.statSync(outputPath);
|
|
10420
10701
|
const hash = await DeltaGenerator.computeHash(outputPath);
|
|
10421
10702
|
return {
|
|
10422
10703
|
size: stats.size,
|
|
10423
10704
|
sha256: hash
|
|
10424
10705
|
};
|
|
10425
10706
|
} catch {
|
|
10426
|
-
|
|
10427
|
-
const stats =
|
|
10707
|
+
import_child_process3.execFileSync("xdelta3", ["-e", "-s", oldPath, newPath, outputPath]);
|
|
10708
|
+
const stats = import_fs4.statSync(outputPath);
|
|
10428
10709
|
const hash = await DeltaGenerator.computeHash(outputPath);
|
|
10429
10710
|
return {
|
|
10430
10711
|
size: stats.size,
|
|
@@ -10434,15 +10715,15 @@ class DeltaGenerator {
|
|
|
10434
10715
|
}
|
|
10435
10716
|
static async apply(basePath, deltaPath, outputPath) {
|
|
10436
10717
|
try {
|
|
10437
|
-
|
|
10718
|
+
import_child_process3.execFileSync("bspatch", [basePath, outputPath, deltaPath]);
|
|
10438
10719
|
} catch {
|
|
10439
|
-
|
|
10720
|
+
import_child_process3.execFileSync("xdelta3", ["-d", "-s", basePath, deltaPath, outputPath]);
|
|
10440
10721
|
}
|
|
10441
10722
|
}
|
|
10442
10723
|
static async computeHash(filePath) {
|
|
10443
10724
|
return new Promise((resolve, reject) => {
|
|
10444
10725
|
const hash = import_crypto3.createHash("sha256");
|
|
10445
|
-
const stream =
|
|
10726
|
+
const stream = import_fs4.createReadStream(filePath);
|
|
10446
10727
|
stream.on("data", (chunk) => hash.update(chunk));
|
|
10447
10728
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
10448
10729
|
stream.on("error", reject);
|
|
@@ -10459,7 +10740,7 @@ function generateUpdateManifest(options) {
|
|
|
10459
10740
|
for (const [platform, info] of Object.entries(options.platforms)) {
|
|
10460
10741
|
if (!info)
|
|
10461
10742
|
continue;
|
|
10462
|
-
const bytes =
|
|
10743
|
+
const bytes = import_fs4.readFileSync(info.path);
|
|
10463
10744
|
const hash = import_crypto3.createHash("sha256").update(bytes).digest("hex");
|
|
10464
10745
|
manifest.platforms[platform] = {
|
|
10465
10746
|
url: info.url,
|
|
@@ -10469,7 +10750,7 @@ function generateUpdateManifest(options) {
|
|
|
10469
10750
|
};
|
|
10470
10751
|
const platformDeltas = options.deltas?.filter((d) => d.platform === platform) || [];
|
|
10471
10752
|
for (const delta of platformDeltas) {
|
|
10472
|
-
const deltaBytes =
|
|
10753
|
+
const deltaBytes = import_fs4.readFileSync(delta.path);
|
|
10473
10754
|
const deltaHash = import_crypto3.createHash("sha256").update(deltaBytes).digest("hex");
|
|
10474
10755
|
manifest.platforms[platform].delta.push({
|
|
10475
10756
|
fromVersion: delta.fromVersion,
|
|
@@ -10538,7 +10819,7 @@ ${update.releaseNotes}`);
|
|
|
10538
10819
|
releaseDate: new Date().toISOString(),
|
|
10539
10820
|
platforms: {}
|
|
10540
10821
|
};
|
|
10541
|
-
|
|
10822
|
+
import_fs4.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
|
|
10542
10823
|
console.log(`Manifest generated: ${outputPath}`);
|
|
10543
10824
|
console.log("Edit the file to add platform-specific update URLs");
|
|
10544
10825
|
break;
|
|
@@ -10564,7 +10845,7 @@ Examples:
|
|
|
10564
10845
|
var updater_default = AutoUpdater;
|
|
10565
10846
|
|
|
10566
10847
|
// src/index.ts
|
|
10567
|
-
var
|
|
10848
|
+
var execFileAsync2 = import_node_util.promisify(import_node_child_process.execFile);
|
|
10568
10849
|
var versionMismatchWarned = false;
|
|
10569
10850
|
function getSdkVersion() {
|
|
10570
10851
|
try {
|
|
@@ -10578,7 +10859,7 @@ async function probeBinaryVersion(craftPath) {
|
|
|
10578
10859
|
if (versionMismatchWarned)
|
|
10579
10860
|
return;
|
|
10580
10861
|
try {
|
|
10581
|
-
const { stdout } = await
|
|
10862
|
+
const { stdout } = await execFileAsync2(craftPath, ["--version"], { timeout: 2000 });
|
|
10582
10863
|
const nativeVersion = parseCraftVersionOutput(stdout);
|
|
10583
10864
|
const sdkVersion = getSdkVersion();
|
|
10584
10865
|
if (sdkVersion !== "unknown" && nativeVersion && nativeVersion !== sdkVersion) {
|