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.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,
|
|
@@ -9983,24 +9992,236 @@ var commonOptimizations = {
|
|
|
9983
9992
|
// src/updater/index.ts
|
|
9984
9993
|
var exports_updater = {};
|
|
9985
9994
|
__export(exports_updater, {
|
|
9995
|
+
verifyBundleTrust: () => verifyBundleTrust,
|
|
9986
9996
|
updaterCommand: () => updaterCommand,
|
|
9997
|
+
swapBundle: () => swapBundle,
|
|
9998
|
+
readBundleIdentity: () => readBundleIdentity,
|
|
9999
|
+
isMacOS: () => isMacOS,
|
|
9987
10000
|
generateUpdateManifest: () => generateUpdateManifest,
|
|
10001
|
+
extractBundleFromZip: () => extractBundleFromZip,
|
|
10002
|
+
extractBundleFromDmg: () => extractBundleFromDmg,
|
|
10003
|
+
extractBundle: () => extractBundle,
|
|
10004
|
+
dittoBundle: () => dittoBundle,
|
|
9988
10005
|
default: () => updater_default,
|
|
10006
|
+
clearQuarantine: () => clearQuarantine,
|
|
10007
|
+
canReplaceBundle: () => canReplaceBundle,
|
|
9989
10008
|
DeltaGenerator: () => DeltaGenerator,
|
|
9990
10009
|
AutoUpdater: () => AutoUpdater
|
|
9991
10010
|
});
|
|
9992
|
-
var
|
|
9993
|
-
var
|
|
9994
|
-
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");
|
|
9995
10015
|
var import_crypto3 = require("crypto");
|
|
9996
10016
|
var import_events3 = require("events");
|
|
9997
|
-
|
|
9998
|
-
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
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);
|
|
10002
10221
|
}
|
|
10003
10222
|
|
|
10223
|
+
// src/updater/index.ts
|
|
10224
|
+
var PROGRESS_INTERVAL_MS = 100;
|
|
10004
10225
|
class AutoUpdater extends import_events3.EventEmitter {
|
|
10005
10226
|
config;
|
|
10006
10227
|
updateInfo = null;
|
|
@@ -10009,12 +10230,22 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10009
10230
|
cachedEtag = null;
|
|
10010
10231
|
cachedLastModified = null;
|
|
10011
10232
|
cachedManifest = null;
|
|
10233
|
+
lastError = null;
|
|
10012
10234
|
emitError(error) {
|
|
10235
|
+
this.lastError = error instanceof Error ? error : new Error(String(error));
|
|
10013
10236
|
if (this.listenerCount("error") > 0)
|
|
10014
10237
|
this.emit("error", error);
|
|
10015
10238
|
else
|
|
10016
10239
|
console.error("[Updater]", error);
|
|
10017
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
|
+
}
|
|
10018
10249
|
constructor(config) {
|
|
10019
10250
|
super();
|
|
10020
10251
|
const url = config.updateUrl;
|
|
@@ -10048,6 +10279,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10048
10279
|
}
|
|
10049
10280
|
async checkForUpdates() {
|
|
10050
10281
|
this.emit("checking-for-update");
|
|
10282
|
+
this.lastError = null;
|
|
10051
10283
|
try {
|
|
10052
10284
|
const platform = this.getPlatform();
|
|
10053
10285
|
const url = `${this.config.updateUrl}?v=${this.config.currentVersion}&channel=${this.config.channel}&platform=${platform}`;
|
|
@@ -10096,20 +10328,21 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10096
10328
|
if (!this.updateInfo) {
|
|
10097
10329
|
throw new Error("No update available");
|
|
10098
10330
|
}
|
|
10331
|
+
this.lastError = null;
|
|
10099
10332
|
const platform = this.getPlatform();
|
|
10100
10333
|
const platformUpdate = this.updateInfo.platforms[platform];
|
|
10101
10334
|
if (!platformUpdate) {
|
|
10102
10335
|
throw new Error(`No update available for platform: ${platform}`);
|
|
10103
10336
|
}
|
|
10104
|
-
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;
|
|
10105
10338
|
const _updateSource = deltaUpdate || platformUpdate;
|
|
10106
10339
|
const url = deltaUpdate?.url || platformUpdate.url;
|
|
10107
10340
|
const expectedSize = deltaUpdate?.size || platformUpdate.size;
|
|
10108
10341
|
const expectedHash = deltaUpdate?.sha256 || platformUpdate.sha256;
|
|
10109
|
-
const downloadDir =
|
|
10110
|
-
|
|
10111
|
-
const fileName =
|
|
10112
|
-
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);
|
|
10113
10346
|
try {
|
|
10114
10347
|
const response = await fetch(url);
|
|
10115
10348
|
if (!response.ok) {
|
|
@@ -10119,63 +10352,83 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10119
10352
|
const total = contentLength || expectedSize;
|
|
10120
10353
|
let downloaded = 0;
|
|
10121
10354
|
const startTime = Date.now();
|
|
10122
|
-
const fileStream =
|
|
10355
|
+
const fileStream = import_fs4.createWriteStream(this.downloadPath);
|
|
10123
10356
|
const reader = response.body?.getReader();
|
|
10124
10357
|
if (!reader) {
|
|
10125
10358
|
throw new Error("Failed to get response reader");
|
|
10126
10359
|
}
|
|
10360
|
+
const digest = import_crypto3.createHash("sha256");
|
|
10361
|
+
let lastProgressAt = 0;
|
|
10127
10362
|
while (true) {
|
|
10128
10363
|
const { done, value } = await reader.read();
|
|
10129
10364
|
if (done)
|
|
10130
10365
|
break;
|
|
10131
|
-
|
|
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
|
+
}
|
|
10132
10381
|
downloaded += value.length;
|
|
10133
|
-
const
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
10140
|
-
|
|
10141
|
-
|
|
10142
|
-
|
|
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
|
+
}
|
|
10143
10396
|
}
|
|
10144
10397
|
fileStream.end();
|
|
10145
10398
|
await new Promise((resolve, reject) => {
|
|
10146
10399
|
fileStream.once("finish", resolve);
|
|
10147
10400
|
fileStream.once("error", reject);
|
|
10148
10401
|
});
|
|
10149
|
-
const hash =
|
|
10402
|
+
const hash = digest.digest("hex");
|
|
10150
10403
|
if (hash !== expectedHash) {
|
|
10151
|
-
|
|
10404
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10152
10405
|
throw new Error("Download verification failed: hash mismatch");
|
|
10153
10406
|
}
|
|
10154
10407
|
if (deltaUpdate) {
|
|
10155
|
-
if (!
|
|
10408
|
+
if (!import_fs4.statSync(this.config.appPath).isFile()) {
|
|
10156
10409
|
throw new Error("Delta updates require appPath to point to the installed bundle file");
|
|
10157
10410
|
}
|
|
10158
10411
|
const patchPath = this.downloadPath;
|
|
10159
|
-
const reconstructedPath =
|
|
10412
|
+
const reconstructedPath = import_path3.join(downloadDir, `reconstructed-${import_path3.basename(platformUpdate.url)}`);
|
|
10160
10413
|
await DeltaGenerator.apply(this.config.appPath, patchPath, reconstructedPath);
|
|
10161
10414
|
const reconstructedHash = await this.computeFileHash(reconstructedPath);
|
|
10162
10415
|
if (reconstructedHash !== platformUpdate.sha256) {
|
|
10163
10416
|
try {
|
|
10164
|
-
|
|
10417
|
+
import_fs4.unlinkSync(reconstructedPath);
|
|
10165
10418
|
} catch {}
|
|
10166
10419
|
throw new Error("Delta reconstruction failed: full bundle hash mismatch");
|
|
10167
10420
|
}
|
|
10168
|
-
|
|
10421
|
+
import_fs4.unlinkSync(patchPath);
|
|
10169
10422
|
this.downloadPath = reconstructedPath;
|
|
10170
10423
|
}
|
|
10171
10424
|
if (this.config.publicKeyPem && !platformUpdate.signature) {
|
|
10172
|
-
|
|
10425
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10173
10426
|
throw new Error("Updater: publicKeyPem is configured but the manifest entry has no signature. " + "Refusing to install unsigned update.");
|
|
10174
10427
|
}
|
|
10175
10428
|
if (platformUpdate.signature) {
|
|
10176
10429
|
const valid = await this.verifySignature(this.downloadPath, platformUpdate.signature);
|
|
10177
10430
|
if (!valid) {
|
|
10178
|
-
|
|
10431
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10179
10432
|
throw new Error("Download verification failed: invalid signature");
|
|
10180
10433
|
}
|
|
10181
10434
|
}
|
|
@@ -10194,7 +10447,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10194
10447
|
}
|
|
10195
10448
|
}
|
|
10196
10449
|
async installUpdate(restartAfter = true) {
|
|
10197
|
-
if (!this.downloadPath || !
|
|
10450
|
+
if (!this.downloadPath || !import_fs4.existsSync(this.downloadPath)) {
|
|
10198
10451
|
throw new Error("No update downloaded");
|
|
10199
10452
|
}
|
|
10200
10453
|
if (this.updateInfo) {
|
|
@@ -10204,7 +10457,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10204
10457
|
const hash = await this.computeFileHash(this.downloadPath);
|
|
10205
10458
|
if (hash !== platformUpdate.sha256) {
|
|
10206
10459
|
try {
|
|
10207
|
-
|
|
10460
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10208
10461
|
} catch {}
|
|
10209
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.");
|
|
10210
10463
|
}
|
|
@@ -10215,7 +10468,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10215
10468
|
const valid = await this.verifySignature(this.downloadPath, platformUpdate.signature);
|
|
10216
10469
|
if (!valid) {
|
|
10217
10470
|
try {
|
|
10218
|
-
|
|
10471
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10219
10472
|
} catch {}
|
|
10220
10473
|
throw new Error("Updater.installUpdate: invalid signature on downloaded bundle");
|
|
10221
10474
|
}
|
|
@@ -10238,14 +10491,17 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10238
10491
|
await this.installLinuxUpdate();
|
|
10239
10492
|
break;
|
|
10240
10493
|
}
|
|
10241
|
-
if (this.downloadPath &&
|
|
10242
|
-
|
|
10494
|
+
if (this.downloadPath && import_fs4.existsSync(this.downloadPath)) {
|
|
10495
|
+
import_fs4.unlinkSync(this.downloadPath);
|
|
10243
10496
|
}
|
|
10497
|
+
try {
|
|
10498
|
+
import_fs4.rmdirSync(this.downloadDir());
|
|
10499
|
+
} catch {}
|
|
10244
10500
|
progress.phase = "done";
|
|
10245
10501
|
progress.percent = 100;
|
|
10246
10502
|
this.emit("download-progress", progress);
|
|
10247
10503
|
if (restartAfter) {
|
|
10248
|
-
this.restartApp();
|
|
10504
|
+
await this.restartApp();
|
|
10249
10505
|
}
|
|
10250
10506
|
} catch (error) {
|
|
10251
10507
|
progress.phase = "error";
|
|
@@ -10256,54 +10512,61 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10256
10512
|
async installMacOSUpdate() {
|
|
10257
10513
|
const downloadPath = this.downloadPath;
|
|
10258
10514
|
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")) {
|
|
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 {
|
|
10284
10534
|
try {
|
|
10285
|
-
|
|
10286
|
-
} catch
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
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}`);
|
|
10290
10552
|
}
|
|
10553
|
+
import_child_process3.execFileSync("sudo", ["installer", "-pkg", downloadPath, "-target", "/"]);
|
|
10291
10554
|
}
|
|
10292
10555
|
async installWindowsUpdate() {
|
|
10293
10556
|
const downloadPath = this.downloadPath;
|
|
10294
10557
|
if (downloadPath.endsWith(".exe")) {
|
|
10295
|
-
|
|
10558
|
+
import_child_process3.spawn(downloadPath, ["/S", "/SILENT", "/VERYSILENT"], {
|
|
10296
10559
|
detached: true,
|
|
10297
10560
|
stdio: "ignore"
|
|
10298
10561
|
});
|
|
10299
10562
|
} else if (downloadPath.endsWith(".msi")) {
|
|
10300
|
-
|
|
10563
|
+
import_child_process3.spawn("msiexec", ["/i", downloadPath, "/quiet", "/norestart"], {
|
|
10301
10564
|
detached: true,
|
|
10302
10565
|
stdio: "ignore"
|
|
10303
10566
|
});
|
|
10304
10567
|
} else if (downloadPath.endsWith(".zip")) {
|
|
10305
|
-
const appDir =
|
|
10306
|
-
|
|
10568
|
+
const appDir = import_path3.dirname(this.config.appPath);
|
|
10569
|
+
import_child_process3.execFileSync("powershell", [
|
|
10307
10570
|
"-NoProfile",
|
|
10308
10571
|
"-NonInteractive",
|
|
10309
10572
|
"-Command",
|
|
@@ -10316,30 +10579,31 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10316
10579
|
async installLinuxUpdate() {
|
|
10317
10580
|
const downloadPath = this.downloadPath;
|
|
10318
10581
|
if (downloadPath.endsWith(".AppImage")) {
|
|
10319
|
-
|
|
10320
|
-
const { renameSync } = await import("fs");
|
|
10321
|
-
|
|
10582
|
+
import_child_process3.execFileSync("chmod", ["+x", downloadPath]);
|
|
10583
|
+
const { renameSync: renameSync2 } = await import("fs");
|
|
10584
|
+
renameSync2(downloadPath, this.config.appPath);
|
|
10322
10585
|
} else if (downloadPath.endsWith(".deb")) {
|
|
10323
|
-
|
|
10586
|
+
import_child_process3.execFileSync("sudo", ["dpkg", "-i", downloadPath]);
|
|
10324
10587
|
} else if (downloadPath.endsWith(".rpm")) {
|
|
10325
|
-
|
|
10588
|
+
import_child_process3.execFileSync("sudo", ["rpm", "-U", downloadPath]);
|
|
10326
10589
|
} else if (downloadPath.endsWith(".tar.gz")) {
|
|
10327
|
-
const appDir =
|
|
10328
|
-
|
|
10590
|
+
const appDir = import_path3.dirname(this.config.appPath);
|
|
10591
|
+
import_child_process3.execFileSync("tar", ["-xzf", downloadPath, "-C", appDir]);
|
|
10329
10592
|
}
|
|
10330
10593
|
}
|
|
10331
|
-
restartApp() {
|
|
10332
|
-
const platform = this.getPlatform();
|
|
10594
|
+
async restartApp() {
|
|
10333
10595
|
const appPath = this.config.appPath;
|
|
10334
|
-
|
|
10596
|
+
if (this.config.relaunch) {
|
|
10597
|
+
await this.config.relaunch(appPath);
|
|
10598
|
+
return;
|
|
10599
|
+
}
|
|
10600
|
+
switch (this.getPlatform()) {
|
|
10335
10601
|
case "darwin":
|
|
10336
|
-
|
|
10602
|
+
import_child_process3.spawn("open", ["-n", appPath], { detached: true, stdio: "ignore" });
|
|
10337
10603
|
break;
|
|
10338
10604
|
case "win32":
|
|
10339
|
-
import_child_process2.spawn(appPath, [], { detached: true, stdio: "ignore" });
|
|
10340
|
-
break;
|
|
10341
10605
|
case "linux":
|
|
10342
|
-
|
|
10606
|
+
import_child_process3.spawn(appPath, [], { detached: true, stdio: "ignore" });
|
|
10343
10607
|
break;
|
|
10344
10608
|
}
|
|
10345
10609
|
process.exit(0);
|
|
@@ -10370,7 +10634,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10370
10634
|
async computeFileHash(filePath) {
|
|
10371
10635
|
return new Promise((resolve, reject) => {
|
|
10372
10636
|
const hash = import_crypto3.createHash("sha256");
|
|
10373
|
-
const stream =
|
|
10637
|
+
const stream = import_fs4.createReadStream(filePath);
|
|
10374
10638
|
stream.on("data", (chunk) => hash.update(chunk));
|
|
10375
10639
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
10376
10640
|
stream.on("error", reject);
|
|
@@ -10395,7 +10659,7 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10395
10659
|
} catch {
|
|
10396
10660
|
return false;
|
|
10397
10661
|
}
|
|
10398
|
-
const data =
|
|
10662
|
+
const data = import_fs4.readFileSync(filePath);
|
|
10399
10663
|
if (algo === "ed25519") {
|
|
10400
10664
|
return verify(null, data, publicKey, sigBytes);
|
|
10401
10665
|
}
|
|
@@ -10413,18 +10677,32 @@ class AutoUpdater extends import_events3.EventEmitter {
|
|
|
10413
10677
|
}
|
|
10414
10678
|
|
|
10415
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
|
+
}
|
|
10416
10694
|
static async generate(oldPath, newPath, outputPath) {
|
|
10417
10695
|
try {
|
|
10418
|
-
|
|
10419
|
-
const stats =
|
|
10696
|
+
import_child_process3.execFileSync("bsdiff", [oldPath, newPath, outputPath]);
|
|
10697
|
+
const stats = import_fs4.statSync(outputPath);
|
|
10420
10698
|
const hash = await DeltaGenerator.computeHash(outputPath);
|
|
10421
10699
|
return {
|
|
10422
10700
|
size: stats.size,
|
|
10423
10701
|
sha256: hash
|
|
10424
10702
|
};
|
|
10425
10703
|
} catch {
|
|
10426
|
-
|
|
10427
|
-
const stats =
|
|
10704
|
+
import_child_process3.execFileSync("xdelta3", ["-e", "-s", oldPath, newPath, outputPath]);
|
|
10705
|
+
const stats = import_fs4.statSync(outputPath);
|
|
10428
10706
|
const hash = await DeltaGenerator.computeHash(outputPath);
|
|
10429
10707
|
return {
|
|
10430
10708
|
size: stats.size,
|
|
@@ -10434,15 +10712,15 @@ class DeltaGenerator {
|
|
|
10434
10712
|
}
|
|
10435
10713
|
static async apply(basePath, deltaPath, outputPath) {
|
|
10436
10714
|
try {
|
|
10437
|
-
|
|
10715
|
+
import_child_process3.execFileSync("bspatch", [basePath, outputPath, deltaPath]);
|
|
10438
10716
|
} catch {
|
|
10439
|
-
|
|
10717
|
+
import_child_process3.execFileSync("xdelta3", ["-d", "-s", basePath, deltaPath, outputPath]);
|
|
10440
10718
|
}
|
|
10441
10719
|
}
|
|
10442
10720
|
static async computeHash(filePath) {
|
|
10443
10721
|
return new Promise((resolve, reject) => {
|
|
10444
10722
|
const hash = import_crypto3.createHash("sha256");
|
|
10445
|
-
const stream =
|
|
10723
|
+
const stream = import_fs4.createReadStream(filePath);
|
|
10446
10724
|
stream.on("data", (chunk) => hash.update(chunk));
|
|
10447
10725
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
10448
10726
|
stream.on("error", reject);
|
|
@@ -10459,7 +10737,7 @@ function generateUpdateManifest(options) {
|
|
|
10459
10737
|
for (const [platform, info] of Object.entries(options.platforms)) {
|
|
10460
10738
|
if (!info)
|
|
10461
10739
|
continue;
|
|
10462
|
-
const bytes =
|
|
10740
|
+
const bytes = import_fs4.readFileSync(info.path);
|
|
10463
10741
|
const hash = import_crypto3.createHash("sha256").update(bytes).digest("hex");
|
|
10464
10742
|
manifest.platforms[platform] = {
|
|
10465
10743
|
url: info.url,
|
|
@@ -10469,7 +10747,7 @@ function generateUpdateManifest(options) {
|
|
|
10469
10747
|
};
|
|
10470
10748
|
const platformDeltas = options.deltas?.filter((d) => d.platform === platform) || [];
|
|
10471
10749
|
for (const delta of platformDeltas) {
|
|
10472
|
-
const deltaBytes =
|
|
10750
|
+
const deltaBytes = import_fs4.readFileSync(delta.path);
|
|
10473
10751
|
const deltaHash = import_crypto3.createHash("sha256").update(deltaBytes).digest("hex");
|
|
10474
10752
|
manifest.platforms[platform].delta.push({
|
|
10475
10753
|
fromVersion: delta.fromVersion,
|
|
@@ -10538,7 +10816,7 @@ ${update.releaseNotes}`);
|
|
|
10538
10816
|
releaseDate: new Date().toISOString(),
|
|
10539
10817
|
platforms: {}
|
|
10540
10818
|
};
|
|
10541
|
-
|
|
10819
|
+
import_fs4.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
|
|
10542
10820
|
console.log(`Manifest generated: ${outputPath}`);
|
|
10543
10821
|
console.log("Edit the file to add platform-specific update URLs");
|
|
10544
10822
|
break;
|
|
@@ -10564,7 +10842,7 @@ Examples:
|
|
|
10564
10842
|
var updater_default = AutoUpdater;
|
|
10565
10843
|
|
|
10566
10844
|
// src/index.ts
|
|
10567
|
-
var
|
|
10845
|
+
var execFileAsync2 = import_node_util.promisify(import_node_child_process.execFile);
|
|
10568
10846
|
var versionMismatchWarned = false;
|
|
10569
10847
|
function getSdkVersion() {
|
|
10570
10848
|
try {
|
|
@@ -10578,7 +10856,7 @@ async function probeBinaryVersion(craftPath) {
|
|
|
10578
10856
|
if (versionMismatchWarned)
|
|
10579
10857
|
return;
|
|
10580
10858
|
try {
|
|
10581
|
-
const { stdout } = await
|
|
10859
|
+
const { stdout } = await execFileAsync2(craftPath, ["--version"], { timeout: 2000 });
|
|
10582
10860
|
const nativeVersion = parseCraftVersionOutput(stdout);
|
|
10583
10861
|
const sdkVersion = getSdkVersion();
|
|
10584
10862
|
if (sdkVersion !== "unknown" && nativeVersion && nativeVersion !== sdkVersion) {
|