This repository has been archived by the owner on Jul 18, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 20
/
afterPack.ts
124 lines (113 loc) · 5.02 KB
/
afterPack.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/
import { exec } from "@actions/exec";
import type MacPackager from "app-builder-lib/out/macPackager";
import { log, Arch } from "builder-util";
import { AfterPackContext } from "electron-builder";
import fs from "fs/promises";
import path from "path";
import plist, { PlistObject } from "plist";
async function getKeychainFile(context: AfterPackContext): Promise<string | undefined> {
const macPackager = context.packager as MacPackager;
if ((macPackager as Partial<typeof macPackager>).codeSigningInfo == undefined) {
log.error("No code signing info available.");
return;
}
return (await macPackager.codeSigningInfo.value).keychainFile ?? undefined;
}
export default async function afterPack(context: AfterPackContext): Promise<void> {
await configureQuickLookExtension(context);
}
/**
* Configure the .appex for Quick Look bag preview support. The appex itself is copied by
* electron-builder as part of `extraFiles`.
*
* Here we perform the following steps:
* - Update Info.plist to declare support for .bag file previews
* - Copy the actual preview implementation (outputs from webpack.quicklook.config.ts)
* - Re-codesign the app so it is properly sandboxed (macOS refuses to load an extension if it is
* not sandboxed)
*/
async function configureQuickLookExtension(context: AfterPackContext) {
const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== "darwin") {
return;
}
const appName = context.packager.appInfo.productFilename;
const appPath = path.join(appOutDir, `${appName}.app`);
const appBundleId = context.packager.config.appId;
const appexPath = path.join(appPath, "Contents", "PlugIns", "PreviewExtension.appex");
const appexContents = path.join(appexPath, "Contents");
const appexResources = path.join(appexContents, "Resources");
const appexInfoPlist = path.join(appexContents, "Info.plist");
const appexExecutablePath = path.join(appexContents, "MacOS", "PreviewExtension");
const originalInfo = plist.parse(
await fs.readFile(appexInfoPlist, { encoding: "utf-8" }),
) as PlistObject;
const newInfo = {
...originalInfo,
CFBundleIdentifier: `${appBundleId}.quicklook`,
NSExtension: {
...(originalInfo.NSExtension as PlistObject),
NSExtensionAttributes: {
QLSupportedContentTypes: ["org.ros.bag", "dev.foxglove.mcap"],
QLSupportsSearchableItems: false,
},
},
QLJS: {
...(originalInfo.QLJS as PlistObject),
pagePath: "index.html",
},
};
await fs.writeFile(appexInfoPlist, plist.build(newInfo));
log.info("Updated appex Info.plist for Quick Look");
const webpackOutputDir = path.join("desktop", ".webpack", "quicklook");
for (const file of await fs.readdir(webpackOutputDir, { withFileTypes: true })) {
if (!file.isFile()) {
throw new Error(`Expected only files in Quick Look webpack output, found: ${file.name}`);
}
await fs.copyFile(path.join(webpackOutputDir, file.name), path.join(appexResources, file.name));
}
log.info("Copied .webpack/quicklook into appex resources");
// When building a universal app, electron-builder uses lipo to merge binaries at the same path.
// Since quicklookjs already provides a universal binary, we need to strip out other architectures
// so that lipo doesn't fail when it gets two copies of each slice.
const arch = new Map([
[Arch.arm64, "arm64"],
[Arch.x64, "x86_64"],
[Arch.universal, "universal"],
]).get(context.arch);
if (arch == undefined) {
throw new Error(`Unsupported arch ${context.arch}`);
}
if (arch !== "universal") {
await exec("lipo", ["-extract", arch, appexExecutablePath, "-output", appexExecutablePath]);
log.info(`Extracted ${arch} from appex executable`);
}
// The notarization step requires a valid signature from our "Developer ID Application"
// certificate. However this certificate is only available in CI, so for packaging to succeed in a
// local development workflow, we just use the "-" ad-hoc signing identity.
//
// electron-builder's MacPackager creates a temporary keychain to hold the signing info. The
// certificate is not in the regular system keychain so we have to use the temporary keychain for
// signing.
const keychainFile = await getKeychainFile(context);
if (keychainFile != undefined) {
await exec("security", ["find-identity", "-v", "-p", "codesigning", keychainFile]);
}
const signingArgs =
process.env.CI != undefined && keychainFile != undefined
? ["--keychain", keychainFile, "--sign", "Developer ID Application"]
: ["--sign", "-"];
await exec("codesign", [
...signingArgs,
"--force",
"--options",
"runtime", // notarization requires Hardened Runtime to be enabled
"--entitlements",
path.join("node_modules", "quicklookjs", "dist", "PreviewExtension.entitlements"),
appexPath,
]);
log.info("Re-sandboxed appex");
}