2022-12-27 15:37:40 +01:00
|
|
|
import { resolveLibs } from "./RollupConfig.js"
|
2019-09-13 13:49:11 +02:00
|
|
|
import nodeResolve from "@rollup/plugin-node-resolve"
|
2023-04-20 17:14:30 +02:00
|
|
|
import fs from "node:fs"
|
|
|
|
|
import path, { dirname } from "node:path"
|
2022-12-27 15:37:40 +01:00
|
|
|
import { rollup } from "rollup"
|
2023-03-28 15:34:58 +02:00
|
|
|
import terser from "@rollup/plugin-terser"
|
2019-09-13 13:49:11 +02:00
|
|
|
import electronBuilder from "electron-builder"
|
2022-03-02 18:21:01 +01:00
|
|
|
import generatePackageJson from "./electron-package-json-template.js"
|
2022-12-27 15:37:40 +01:00
|
|
|
import { create as createEnv, preludeEnvPlugin } from "./env.js"
|
2023-04-20 17:14:30 +02:00
|
|
|
import cp from "node:child_process"
|
|
|
|
|
import util from "node:util"
|
2022-01-07 16:52:02 +01:00
|
|
|
import typescript from "@rollup/plugin-typescript"
|
2023-04-20 17:14:30 +02:00
|
|
|
import { fileURLToPath } from "node:url"
|
2022-12-27 15:37:40 +01:00
|
|
|
import { getCanonicalPlatformName } from "./buildUtils.js"
|
2023-09-26 18:03:30 +02:00
|
|
|
import { domainConfigs } from "./DomainConfigs.js"
|
2024-01-18 15:57:13 +01:00
|
|
|
import commonjs from "@rollup/plugin-commonjs"
|
2025-01-02 18:47:57 +01:00
|
|
|
import { nodeGypPlugin } from "./nodeGypPlugin.js"
|
|
|
|
|
import { napiPlugin } from "./napiPlugin.js"
|
2025-07-17 17:57:30 +02:00
|
|
|
import replace from "@rollup/plugin-replace"
|
2018-09-24 11:56:41 +02:00
|
|
|
|
2021-08-11 17:53:48 +02:00
|
|
|
const exec = util.promisify(cp.exec)
|
2022-01-12 14:43:01 +01:00
|
|
|
const buildSrc = dirname(fileURLToPath(import.meta.url))
|
|
|
|
|
const projectRoot = path.resolve(path.join(buildSrc, ".."))
|
2019-09-13 13:49:11 +02:00
|
|
|
|
2022-02-21 15:17:37 +01:00
|
|
|
/**
|
|
|
|
|
* @param dirname directory this was called from
|
|
|
|
|
* @param version application version that gets built
|
2025-01-02 18:47:57 +01:00
|
|
|
* @param platform {"linux"|"win32"|"darwin"} - Canonical platform name of the desktop target to be built
|
|
|
|
|
* @param architecture {"arm64"|"x64"|"universal"} the instruction set used in the built desktop binary
|
2022-02-21 15:17:37 +01:00
|
|
|
* @param updateUrl where the client should pull its updates from, if any
|
|
|
|
|
* @param nameSuffix suffix used to distinguish test-, prod- or snapshot builds on the same machine
|
2025-01-02 18:47:57 +01:00
|
|
|
* @param notarize {boolean} for the macOS notarization feature
|
2022-02-21 15:17:37 +01:00
|
|
|
* @param outDir where copy the finished artifacts
|
|
|
|
|
* @param unpacked output desktop client without packing it into an installer
|
2025-01-02 18:47:57 +01:00
|
|
|
* @param [disableMinify] {boolean} whether to disible code minified
|
2022-02-21 15:17:37 +01:00
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
2025-03-10 16:19:11 +01:00
|
|
|
export async function buildDesktop({
|
|
|
|
|
dirname,
|
|
|
|
|
version,
|
|
|
|
|
platform,
|
|
|
|
|
architecture,
|
|
|
|
|
updateUrl,
|
|
|
|
|
nameSuffix,
|
|
|
|
|
notarize,
|
|
|
|
|
outDir,
|
|
|
|
|
unpacked,
|
|
|
|
|
disableMinify,
|
|
|
|
|
networkDebugging,
|
|
|
|
|
}) {
|
2019-09-13 13:49:11 +02:00
|
|
|
// The idea is that we
|
2023-09-27 17:22:36 +02:00
|
|
|
// - build desktop code into build/desktop
|
2019-09-13 13:49:11 +02:00
|
|
|
// - package the whole dist directory into the app
|
|
|
|
|
// - move installers out of the dist into build/desktop-whatever
|
|
|
|
|
// - cleanup dist directory
|
|
|
|
|
// It's messy
|
2022-02-21 15:17:37 +01:00
|
|
|
|
2023-11-17 17:51:46 +01:00
|
|
|
console.log(`Building ${architecture} ${platform} desktop client for v${version}`)
|
2023-10-11 17:50:52 +02:00
|
|
|
updateUrl = updateUrl?.toString()
|
2022-03-02 18:21:01 +01:00
|
|
|
const updateSubDir = `desktop${nameSuffix}`
|
2023-10-05 10:51:49 +02:00
|
|
|
const distDir = path.join(dirname, "build")
|
2023-10-19 11:17:31 +02:00
|
|
|
|
|
|
|
|
// this prevents us from outputting artifacts into the "desktop" build folder that contains the desktop clients js files that get bundled
|
|
|
|
|
outDir = path.join(outDir ?? distDir, "..", "artifacts", updateSubDir)
|
2023-10-19 13:33:29 +02:00
|
|
|
await fs.promises.rm(outDir, { recursive: true, force: true })
|
2022-12-27 15:37:40 +01:00
|
|
|
await fs.promises.mkdir(outDir, { recursive: true })
|
2019-11-07 16:08:49 +01:00
|
|
|
|
2019-09-13 13:49:11 +02:00
|
|
|
// We need to get the right build of native dependencies. There's a tool called node-gyp which can build for different architectures
|
|
|
|
|
// and downloads everything it needs. Usually dependencies build themselves in post-install script.
|
2024-01-10 16:11:26 +01:00
|
|
|
// Currently we have sqlite which avoids building itself if possible and only build
|
2018-12-13 17:59:47 +01:00
|
|
|
console.log("Updating electron-builder config...")
|
2022-05-10 16:36:19 +02:00
|
|
|
const content = await generatePackageJson({
|
2019-09-13 13:49:11 +02:00
|
|
|
nameSuffix,
|
|
|
|
|
version,
|
|
|
|
|
updateUrl,
|
2022-08-05 11:10:36 +02:00
|
|
|
iconPath: path.join(dirname, "/resources/desktop-icons/logo-solo-red.png" + (platform === "win32" ? ".ico" : "")),
|
2019-09-13 13:49:11 +02:00
|
|
|
notarize,
|
|
|
|
|
unpacked,
|
2022-07-29 10:19:32 +02:00
|
|
|
sign: (process.env.DEBUG_SIGN && updateUrl !== "") || !!process.env.JENKINS_HOME,
|
2023-11-17 17:51:46 +01:00
|
|
|
architecture,
|
2020-04-07 10:32:03 +02:00
|
|
|
})
|
2019-09-13 13:49:11 +02:00
|
|
|
console.log("updateUrl is", updateUrl)
|
2023-09-27 17:22:36 +02:00
|
|
|
await fs.promises.writeFile("./build/package.json", JSON.stringify(content), "utf-8")
|
2022-03-02 18:21:01 +01:00
|
|
|
if (platform === "win32") await getMapirs(distDir)
|
2021-06-17 14:33:18 +02:00
|
|
|
|
2019-09-13 13:49:11 +02:00
|
|
|
// prepare files
|
|
|
|
|
try {
|
2023-10-05 10:51:49 +02:00
|
|
|
await fs.promises.rm(path.join(distDir, updateSubDir), { recursive: true })
|
2019-09-13 13:49:11 +02:00
|
|
|
} catch (e) {
|
2022-12-27 15:37:40 +01:00
|
|
|
if (e.code !== "ENOENT") {
|
2019-09-13 13:49:11 +02:00
|
|
|
throw e
|
2018-12-13 17:59:47 +01:00
|
|
|
}
|
|
|
|
|
}
|
2022-01-12 14:43:01 +01:00
|
|
|
|
2019-09-13 13:49:11 +02:00
|
|
|
console.log("Bundling desktop client")
|
2025-03-10 16:19:11 +01:00
|
|
|
await rollupDesktop(dirname, path.join(distDir, "desktop"), version, platform, architecture, disableMinify, networkDebugging)
|
2018-12-13 17:59:47 +01:00
|
|
|
|
2019-09-13 13:49:11 +02:00
|
|
|
console.log("Starting installer build...")
|
2021-08-11 17:53:48 +02:00
|
|
|
if (process.platform.startsWith("darwin")) {
|
|
|
|
|
// dmg-license is required by electron to build the mac installer
|
|
|
|
|
// We can't put dmg-license as a dependency in package.json because
|
|
|
|
|
// it will cause npm install to fail if you do it in linux or windows
|
|
|
|
|
// We could install it in mac and then it will be in package-lock.json
|
|
|
|
|
// but then we will have to be vigilant that it doesn't get removed ever
|
|
|
|
|
await exec("npm install dmg-license")
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-13 13:49:11 +02:00
|
|
|
// package for linux, win, mac
|
|
|
|
|
await electronBuilder.build({
|
2024-07-05 19:02:03 +02:00
|
|
|
// @ts-ignore this is the argument to the cli but it's not in ts types?
|
2022-12-27 15:37:40 +01:00
|
|
|
_: ["build"],
|
2022-03-02 18:21:01 +01:00
|
|
|
win: platform === "win32" ? [] : undefined,
|
|
|
|
|
mac: platform === "darwin" ? [] : undefined,
|
|
|
|
|
linux: platform === "linux" ? [] : undefined,
|
2022-12-27 15:37:40 +01:00
|
|
|
publish: "always",
|
|
|
|
|
project: distDir,
|
2019-09-13 13:49:11 +02:00
|
|
|
})
|
|
|
|
|
console.log("Move output to ", outDir)
|
|
|
|
|
await Promise.all(
|
2022-12-27 15:37:40 +01:00
|
|
|
fs
|
|
|
|
|
.readdirSync(path.join(distDir, "/installers"))
|
|
|
|
|
.filter((file) => file.startsWith(content.name) || file.endsWith(".yml") || file.endsWith("-unpacked"))
|
|
|
|
|
.map((file) => fs.promises.rename(path.join(distDir, "/installers/", file), path.join(outDir, file))),
|
2019-09-13 13:49:11 +02:00
|
|
|
)
|
|
|
|
|
await Promise.all([
|
2023-04-24 09:42:07 +02:00
|
|
|
fs.promises.rm(path.join(distDir, "/installers/"), { recursive: true, force: true }),
|
|
|
|
|
fs.promises.rm(path.join(distDir, "/node_modules/"), { recursive: true, force: true }),
|
2022-12-27 15:37:40 +01:00
|
|
|
fs.promises.unlink(path.join(distDir, "/package.json")),
|
|
|
|
|
fs.promises.unlink(path.join(distDir, "/package-lock.json")),
|
2019-09-13 13:49:11 +02:00
|
|
|
])
|
|
|
|
|
}
|
2018-12-13 17:59:47 +01:00
|
|
|
|
2025-03-10 16:19:11 +01:00
|
|
|
async function rollupDesktop(dirname, outDir, version, platform, architecture, disableMinify, networkDebugging) {
|
2022-05-06 08:57:50 +02:00
|
|
|
platform = getCanonicalPlatformName(platform)
|
2019-09-13 13:49:11 +02:00
|
|
|
const mainBundle = await rollup({
|
2024-07-30 17:06:12 +02:00
|
|
|
input: [path.join(dirname, "src/common/desktop/DesktopMain.ts"), path.join(dirname, "src/common/desktop/sqlworker.ts")],
|
2022-07-08 12:58:33 +02:00
|
|
|
// some transitive dep of a transitive dev-dep requires https://www.npmjs.com/package/url
|
|
|
|
|
// which rollup for some reason won't distinguish from the node builtin.
|
2024-12-20 19:48:47 +01:00
|
|
|
external: (id, parent, isResolved) => {
|
|
|
|
|
if (parent != null && parent.endsWith("node-mimimi/dist/binding.cjs")) return true
|
|
|
|
|
if (id.endsWith(".node")) return true
|
|
|
|
|
return ["url", "util", "path", "fs", "os", "http", "https", "crypto", "child_process", "electron"].includes(id)
|
|
|
|
|
},
|
2021-02-23 08:36:38 +01:00
|
|
|
preserveEntrySignatures: false,
|
2019-09-13 13:49:11 +02:00
|
|
|
plugins: [
|
2025-07-17 17:57:30 +02:00
|
|
|
replace({
|
|
|
|
|
// AppType.Integrated
|
|
|
|
|
// see src/common/misc/ClientConstants.ts
|
|
|
|
|
APP_TYPE: JSON.stringify("0"),
|
|
|
|
|
}),
|
2025-01-02 18:47:57 +01:00
|
|
|
nodeGypPlugin({
|
2023-04-24 09:42:07 +02:00
|
|
|
rootDir: projectRoot,
|
2025-04-25 13:43:07 +02:00
|
|
|
platform: platform,
|
|
|
|
|
architecture: architecture,
|
|
|
|
|
nodeModule: "@signalapp/sqlcipher",
|
|
|
|
|
// we build for Electron, but it uses NAPI so it's fine to build for node
|
|
|
|
|
environment: "node",
|
|
|
|
|
targetName: "node_sqlcipher",
|
2023-04-24 09:42:07 +02:00
|
|
|
}),
|
2025-07-08 18:31:10 +02:00
|
|
|
// the build script for simple-windows-notifications does not build anything on non-win32 so we get errors when trying to copy files
|
|
|
|
|
platform === "win32"
|
|
|
|
|
? nodeGypPlugin({
|
|
|
|
|
rootDir: projectRoot,
|
|
|
|
|
platform,
|
|
|
|
|
architecture,
|
|
|
|
|
nodeModule: "@indutny/simple-windows-notifications",
|
|
|
|
|
environment: "node",
|
|
|
|
|
targetName: "simple-windows-notifications",
|
|
|
|
|
})
|
|
|
|
|
: undefined,
|
2025-01-02 18:47:57 +01:00
|
|
|
napiPlugin({
|
2024-12-20 19:48:47 +01:00
|
|
|
platform,
|
|
|
|
|
architecture,
|
|
|
|
|
nodeModule: "@tutao/node-mimimi",
|
|
|
|
|
}),
|
2022-01-07 16:52:02 +01:00
|
|
|
typescript({
|
|
|
|
|
tsconfig: "tsconfig.json",
|
|
|
|
|
outDir,
|
|
|
|
|
}),
|
2019-09-13 13:49:11 +02:00
|
|
|
resolveLibs(),
|
2023-04-24 09:42:07 +02:00
|
|
|
nodeResolve({
|
|
|
|
|
preferBuiltins: true,
|
2025-07-21 13:41:05 +02:00
|
|
|
// do not pull in random packages from node_modules, only our workspace is allowed
|
|
|
|
|
// simple-windows-notifications is our own fork so it's fine to not vendor it
|
|
|
|
|
resolveOnly: [/^@tutao\/.*$/, "@indutny/simple-windows-notifications"],
|
2021-05-03 14:03:52 +02:00
|
|
|
}),
|
2024-01-18 15:57:13 +01:00
|
|
|
commonjs(),
|
2022-05-02 14:30:34 +02:00
|
|
|
disableMinify ? undefined : terser(),
|
2025-03-10 16:19:11 +01:00
|
|
|
preludeEnvPlugin(createEnv({ staticUrl: null, version, mode: "Desktop", dist: true, domainConfigs, networkDebugging })),
|
2022-12-27 15:37:40 +01:00
|
|
|
],
|
2019-09-13 13:49:11 +02:00
|
|
|
})
|
2025-01-02 18:47:57 +01:00
|
|
|
await mainBundle.write({ sourcemap: true, format: "esm", dir: outDir })
|
2024-07-30 17:06:12 +02:00
|
|
|
await fs.promises.copyFile(path.join(dirname, "src/common/desktop/preload.js"), path.join(outDir, "preload.js"))
|
|
|
|
|
await fs.promises.copyFile(path.join(dirname, "src/common/desktop/preload-webdialog.js"), path.join(outDir, "preload-webdialog.js"))
|
2021-06-17 14:33:18 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-30 10:27:02 +02:00
|
|
|
/**
|
|
|
|
|
* get the DLL that's needed for the windows client to handle "Send as Mail..." context
|
|
|
|
|
* menu actions.
|
|
|
|
|
* Tries to get a locally built version before delegating to downloadLatestMapirs
|
|
|
|
|
* @param distDir the directory to put the DLL
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
2021-09-09 15:50:46 +02:00
|
|
|
async function getMapirs(distDir) {
|
|
|
|
|
const dllName = "mapirs.dll"
|
2022-12-27 15:37:40 +01:00
|
|
|
const dllSrc =
|
|
|
|
|
process.platform === "win32"
|
|
|
|
|
? path.join("../mapirs/target/x86_64-pc-windows-msvc/release", dllName)
|
|
|
|
|
: path.join("../mapirs/target/x86_64-pc-windows-gnu/release", dllName)
|
2021-09-09 15:50:46 +02:00
|
|
|
const dllTrg = path.join(distDir, dllName)
|
|
|
|
|
console.log("trying to copy", dllName, "from", dllSrc, "to", dllTrg)
|
|
|
|
|
try {
|
|
|
|
|
await fs.promises.copyFile(dllSrc, dllTrg)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.log("no local", dllName, "found, using release from github")
|
|
|
|
|
await downloadLatestMapirs(dllName, dllTrg)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-30 10:27:02 +02:00
|
|
|
/**
|
|
|
|
|
* get the latest mapirs.dll release from github.
|
|
|
|
|
* @param dllName {string} name of the file that should be downloaded from the latest release
|
|
|
|
|
* @param dllTrg {string} path to put the downloaded file
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
2021-09-09 15:50:46 +02:00
|
|
|
async function downloadLatestMapirs(dllName, dllTrg) {
|
2022-06-24 09:16:49 +02:00
|
|
|
try {
|
2022-12-27 15:37:40 +01:00
|
|
|
const { Octokit } = await import("@octokit/rest")
|
|
|
|
|
const octokit = new Octokit()
|
2022-06-24 09:16:49 +02:00
|
|
|
const opts = {
|
|
|
|
|
owner: "tutao",
|
2022-12-27 15:37:40 +01:00
|
|
|
repo: "mapirs",
|
2021-09-09 15:50:46 +02:00
|
|
|
}
|
2022-06-24 09:16:49 +02:00
|
|
|
console.log("getting latest mapirs release")
|
2022-12-27 15:37:40 +01:00
|
|
|
const res = await octokit.request("GET /repos/{owner}/{repo}/releases/latest", opts)
|
2022-06-24 09:16:49 +02:00
|
|
|
console.log("latest mapirs release", res.url)
|
2022-12-27 15:37:40 +01:00
|
|
|
const asset_id = res.data.assets.find((a) => a.name.startsWith(dllName)).id
|
2022-06-24 09:16:49 +02:00
|
|
|
console.log("Downloading mapirs asset", asset_id)
|
2024-07-05 19:02:03 +02:00
|
|
|
const assetResponse = await octokit.repos.getReleaseAsset({
|
|
|
|
|
...opts,
|
|
|
|
|
asset_id,
|
|
|
|
|
headers: {
|
|
|
|
|
Accept: "application/octet-stream",
|
|
|
|
|
},
|
|
|
|
|
})
|
2022-06-24 09:16:49 +02:00
|
|
|
console.log("Writing mapirs asset")
|
2024-07-05 19:02:03 +02:00
|
|
|
// @ts-ignore not clear how to check for response status so that ts is happy
|
|
|
|
|
await fs.promises.writeFile(dllTrg, Buffer.from(assetResponse.data))
|
2022-06-24 09:16:49 +02:00
|
|
|
console.log("Mapirs downloaded")
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error("Failed to download mapirs!", e)
|
|
|
|
|
throw e
|
|
|
|
|
}
|
2022-12-27 15:37:40 +01:00
|
|
|
}
|