2022-02-14 10:19:20 +01:00
|
|
|
/**
|
|
|
|
* Script to build desktop release versions of the app.
|
|
|
|
*/
|
|
|
|
import * as env from "./buildSrc/env.js"
|
2023-04-20 17:14:30 +02:00
|
|
|
import os from "node:os"
|
2024-07-30 12:07:58 +02:00
|
|
|
import { buildWebapp } from "./buildSrc/buildWebapp.js"
|
|
|
|
import { checkArchitectureIsSupported, getCanonicalPlatformName, getTutanotaAppVersion, measure } from "./buildSrc/buildUtils.js"
|
|
|
|
import { dirname } from "node:path"
|
|
|
|
import { fileURLToPath } from "node:url"
|
|
|
|
import { createHtml } from "./buildSrc/createHtml.js"
|
|
|
|
import { Argument, Option, program } from "commander"
|
|
|
|
import { domainConfigs } from "./buildSrc/DomainConfigs.js"
|
|
|
|
import { BlockList } from "node:net"
|
2022-02-14 10:19:20 +01:00
|
|
|
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
2023-10-19 13:33:29 +02:00
|
|
|
const tutaTestUrl = new URL("https://app.test.tuta.com")
|
2023-09-25 13:28:09 +02:00
|
|
|
const tutaAppUrl = new URL("https://app.tuta.com")
|
2022-02-14 10:19:20 +01:00
|
|
|
|
2022-04-28 14:36:24 +02:00
|
|
|
await program
|
2022-02-14 10:19:20 +01:00
|
|
|
.usage('[options] [test|prod|local|release|host <url>], "release" is default')
|
2023-11-03 17:01:47 +01:00
|
|
|
.description("Main build tool for distributable tuta desktop artifacts.")
|
2022-12-27 15:37:40 +01:00
|
|
|
.addArgument(new Argument("stage").choices(["test", "prod", "local", "host", "release"]).default("release").argOptional())
|
2022-04-28 14:36:24 +02:00
|
|
|
.addArgument(new Argument("host").argOptional())
|
2024-07-26 16:53:31 +02:00
|
|
|
.addOption(new Option("-A, --app <type>", "app to build").choices(["mail", "calendar"]).default("mail"))
|
2023-10-05 10:51:49 +02:00
|
|
|
.option("-e, --existing", "Use existing prebuilt Webapp files in /build/")
|
2022-12-27 15:37:40 +01:00
|
|
|
.option("-p, --platform <platform>", "For which platform to build: linux|win|mac", process.platform)
|
2023-11-20 08:37:27 +01:00
|
|
|
.option("-a, --architecture <architecture>", "For which CPU architecture to build: x64|arm_64|universal", process.arch)
|
2022-12-27 15:37:40 +01:00
|
|
|
.option(
|
|
|
|
"-c,--custom-desktop-release",
|
|
|
|
"use if manually building desktop client from source. doesn't install auto updates, but may still notify about new releases.",
|
|
|
|
)
|
|
|
|
.option("-d,--disable-minify", "disable minification", false)
|
|
|
|
.option("-u,--unpacked", "don't pack the app into an installer")
|
|
|
|
.option("-o,--out-dir <outDir>", "where to copy the client")
|
2022-03-02 18:21:01 +01:00
|
|
|
.action(async (stage, host, opts) => {
|
2022-12-27 15:37:40 +01:00
|
|
|
if ((stage === "host" && host == null) || (stage !== "host" && host != null)) {
|
2022-04-28 14:36:24 +02:00
|
|
|
program.outputHelp()
|
2022-02-14 10:19:20 +01:00
|
|
|
process.exit(1)
|
|
|
|
}
|
2022-04-28 14:36:24 +02:00
|
|
|
|
2022-03-02 18:21:01 +01:00
|
|
|
opts.stage = stage ?? "release"
|
|
|
|
opts.host = host
|
2022-02-14 10:19:20 +01:00
|
|
|
|
2022-03-02 18:21:01 +01:00
|
|
|
if (opts.customDesktopRelease) {
|
|
|
|
console.log(`Custom desktop release - setting platform to ${process.platform}`)
|
|
|
|
opts.platform = process.platform
|
2022-02-14 10:19:20 +01:00
|
|
|
}
|
|
|
|
|
2022-03-02 18:21:01 +01:00
|
|
|
opts.platform = getCanonicalPlatformName(opts.platform)
|
2022-02-14 10:19:20 +01:00
|
|
|
|
2023-11-17 17:51:46 +01:00
|
|
|
if (!checkArchitectureIsSupported(opts.platform, opts.architecture)) {
|
|
|
|
throw new Error(`Platform ${opts.platform} on ${opts.architecture} is not supported`)
|
|
|
|
}
|
2024-07-18 18:36:26 +02:00
|
|
|
const hostDescription = opts.host ? ` (${opts.host})` : ""
|
|
|
|
console.log(`Building desktop client (dist) ${opts.platform}-${opts.architecture} ${opts.stage}${hostDescription}`)
|
2023-11-17 17:51:46 +01:00
|
|
|
|
2022-05-06 09:30:36 +02:00
|
|
|
await doBuild(opts)
|
2022-02-14 10:19:20 +01:00
|
|
|
})
|
2022-03-02 18:21:01 +01:00
|
|
|
.parseAsync(process.argv)
|
2022-02-14 10:19:20 +01:00
|
|
|
|
2022-03-02 18:21:01 +01:00
|
|
|
async function doBuild(opts) {
|
2022-02-14 10:19:20 +01:00
|
|
|
try {
|
|
|
|
measure()
|
2023-04-19 13:58:49 +02:00
|
|
|
const version = await getTutanotaAppVersion()
|
2022-02-14 10:19:20 +01:00
|
|
|
|
2022-03-02 18:21:01 +01:00
|
|
|
if (opts.existing) {
|
2022-02-14 10:19:20 +01:00
|
|
|
console.log("Found existing option (-e). Skipping Webapp build.")
|
|
|
|
} else {
|
2022-03-02 18:21:01 +01:00
|
|
|
if (opts.disableMinify) {
|
2022-02-14 10:19:20 +01:00
|
|
|
console.warn("Minification is disabled")
|
|
|
|
}
|
2022-12-27 15:37:40 +01:00
|
|
|
await buildWebapp({
|
|
|
|
version,
|
|
|
|
stage: opts.stage,
|
|
|
|
host: opts.host,
|
|
|
|
measure,
|
|
|
|
minify: !opts.disableMinify,
|
|
|
|
projectDir: __dirname,
|
2024-07-29 11:33:23 +02:00
|
|
|
app: opts.app,
|
2022-12-27 15:37:40 +01:00
|
|
|
})
|
2022-02-14 10:19:20 +01:00
|
|
|
}
|
|
|
|
|
2022-03-02 18:21:01 +01:00
|
|
|
await buildDesktopClient(version, opts)
|
2022-02-14 10:19:20 +01:00
|
|
|
|
2024-08-29 04:49:04 +07:00
|
|
|
const now = new Date(Date.now()).toTimeString().substring(0, 5)
|
2022-02-14 10:19:20 +01:00
|
|
|
console.log(`\nBuild time: ${measure()}s (${now})`)
|
2023-01-30 12:10:11 +01:00
|
|
|
process.exit(0)
|
2022-02-14 10:19:20 +01:00
|
|
|
} catch (e) {
|
|
|
|
console.error("\nBuild error:", e)
|
|
|
|
process.exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-30 12:07:58 +02:00
|
|
|
async function buildDesktopClient(version, { stage, host, platform, architecture, customDesktopRelease, unpacked, outDir, disableMinify }) {
|
|
|
|
const { buildDesktop } = await import("./buildSrc/DesktopBuilder.js")
|
2023-09-25 13:28:09 +02:00
|
|
|
const updateUrl = new URL(tutaAppUrl)
|
|
|
|
updateUrl.pathname = "desktop"
|
2022-02-14 10:19:20 +01:00
|
|
|
const desktopBaseOpts = {
|
|
|
|
dirname: __dirname,
|
|
|
|
version,
|
2022-03-02 18:21:01 +01:00
|
|
|
platform: platform,
|
2023-11-17 17:51:46 +01:00
|
|
|
architecture,
|
2023-10-11 17:50:52 +02:00
|
|
|
updateUrl: customDesktopRelease ? "" : updateUrl,
|
2022-02-14 10:19:20 +01:00
|
|
|
nameSuffix: "",
|
2022-03-02 18:21:01 +01:00
|
|
|
notarize: !customDesktopRelease,
|
|
|
|
outDir: outDir,
|
2022-05-02 14:30:34 +02:00
|
|
|
unpacked: unpacked,
|
|
|
|
disableMinify,
|
2025-03-10 16:19:11 +01:00
|
|
|
networkDebugging: false,
|
2022-02-14 10:19:20 +01:00
|
|
|
}
|
|
|
|
|
2022-03-02 18:21:01 +01:00
|
|
|
if (stage === "release") {
|
2025-03-10 16:19:11 +01:00
|
|
|
await createHtml(env.create({ staticUrl: tutaAppUrl, version, mode: "Desktop", dist: true, domainConfigs, networkDebugging: false }))
|
2022-02-14 10:19:20 +01:00
|
|
|
await buildDesktop(desktopBaseOpts)
|
2022-12-27 15:37:40 +01:00
|
|
|
if (!customDesktopRelease) {
|
2023-09-25 13:28:09 +02:00
|
|
|
const updateUrl = new URL(tutaTestUrl)
|
|
|
|
updateUrl.pathname = "desktop"
|
2022-12-27 15:37:40 +01:00
|
|
|
// don't build the test version for manual/custom builds
|
2022-02-14 10:19:20 +01:00
|
|
|
const desktopTestOpts = Object.assign({}, desktopBaseOpts, {
|
2023-09-25 13:28:09 +02:00
|
|
|
updateUrl,
|
2022-02-14 10:19:20 +01:00
|
|
|
nameSuffix: "-test",
|
|
|
|
// Do not notarize test build
|
2022-12-27 15:37:40 +01:00
|
|
|
notarize: false,
|
2022-02-14 10:19:20 +01:00
|
|
|
})
|
2025-03-10 16:19:11 +01:00
|
|
|
await createHtml(env.create({ staticUrl: tutaTestUrl, version, mode: "Desktop", dist: true, domainConfigs, networkDebugging: false }))
|
2022-02-14 10:19:20 +01:00
|
|
|
await buildDesktop(desktopTestOpts)
|
|
|
|
}
|
2022-03-02 18:21:01 +01:00
|
|
|
} else if (stage === "local") {
|
2022-02-14 10:19:20 +01:00
|
|
|
// this is the only way to contact the local server from localhost, a VM and
|
|
|
|
// from other machines in the LAN with the same url.
|
2024-07-18 18:36:26 +02:00
|
|
|
const addr = privateIpv4Address() ?? Object.values(os.networkInterfaces())[0]?.address
|
|
|
|
if (addr == null) {
|
|
|
|
throw new Error("Unable to pick auto-update address, please specify manually with 'host'")
|
|
|
|
}
|
2022-02-14 10:19:20 +01:00
|
|
|
const desktopLocalOpts = Object.assign({}, desktopBaseOpts, {
|
|
|
|
version,
|
2024-07-18 18:36:26 +02:00
|
|
|
updateUrl: `http://${addr}:9000/desktop/desktop-snapshot`,
|
2022-02-14 10:19:20 +01:00
|
|
|
nameSuffix: "-snapshot",
|
2022-12-27 15:37:40 +01:00
|
|
|
notarize: false,
|
2025-03-10 16:19:11 +01:00
|
|
|
networkDebugging: true,
|
2022-02-14 10:19:20 +01:00
|
|
|
})
|
2025-03-10 16:19:11 +01:00
|
|
|
await createHtml(env.create({ staticUrl: `http://${addr}:9000`, version, mode: "Desktop", dist: true, domainConfigs, networkDebugging: true }))
|
2022-02-14 10:19:20 +01:00
|
|
|
await buildDesktop(desktopLocalOpts)
|
2022-03-02 18:21:01 +01:00
|
|
|
} else if (stage === "test") {
|
2023-09-25 13:28:09 +02:00
|
|
|
const updateUrl = new URL(tutaTestUrl)
|
|
|
|
updateUrl.pathname = "desktop"
|
2022-02-14 10:19:20 +01:00
|
|
|
const desktopTestOpts = Object.assign({}, desktopBaseOpts, {
|
2023-10-11 17:50:52 +02:00
|
|
|
updateUrl: updateUrl,
|
2022-02-14 10:19:20 +01:00
|
|
|
nameSuffix: "-test",
|
2022-12-27 15:37:40 +01:00
|
|
|
notarize: false,
|
2025-03-10 16:19:11 +01:00
|
|
|
networkDebugging: true,
|
2022-02-14 10:19:20 +01:00
|
|
|
})
|
2025-03-10 16:19:11 +01:00
|
|
|
await createHtml(env.create({ staticUrl: tutaTestUrl, version, mode: "Desktop", dist: true, domainConfigs, networkDebugging: true }))
|
2022-02-14 10:19:20 +01:00
|
|
|
await buildDesktop(desktopTestOpts)
|
2022-03-02 18:21:01 +01:00
|
|
|
} else if (stage === "prod") {
|
2022-02-14 10:19:20 +01:00
|
|
|
const desktopProdOpts = Object.assign({}, desktopBaseOpts, {
|
|
|
|
version,
|
|
|
|
updateUrl: "http://localhost:9000/desktop",
|
2022-12-27 15:37:40 +01:00
|
|
|
notarize: false,
|
2022-02-14 10:19:20 +01:00
|
|
|
})
|
2025-03-10 16:19:11 +01:00
|
|
|
await createHtml(env.create({ staticUrl: tutaAppUrl, version, mode: "Desktop", dist: true, domainConfigs, networkDebugging: false }))
|
2022-02-14 10:19:20 +01:00
|
|
|
await buildDesktop(desktopProdOpts)
|
2022-12-27 15:37:40 +01:00
|
|
|
} else {
|
|
|
|
// stage = host
|
2022-02-14 10:19:20 +01:00
|
|
|
const desktopHostOpts = Object.assign({}, desktopBaseOpts, {
|
|
|
|
version,
|
2024-07-18 18:36:26 +02:00
|
|
|
updateUrl: `${host}/desktop/desktop-snapshot`,
|
2022-02-14 10:19:20 +01:00
|
|
|
nameSuffix: "-snapshot",
|
2022-12-27 15:37:40 +01:00
|
|
|
notarize: false,
|
2025-03-10 16:19:11 +01:00
|
|
|
networkDebugging: true,
|
2022-02-14 10:19:20 +01:00
|
|
|
})
|
2025-03-10 16:19:11 +01:00
|
|
|
await createHtml(env.create({ staticUrl: host, version, mode: "Desktop", dist: true, domainConfigs, networkDebugging: true }))
|
2022-02-14 10:19:20 +01:00
|
|
|
await buildDesktop(desktopHostOpts)
|
|
|
|
}
|
|
|
|
}
|
2024-07-18 18:36:26 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param address {string}
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
function isPrivateIpv4Address(address) {
|
|
|
|
const privateRanges = new BlockList()
|
|
|
|
privateRanges.addRange("10.0.0.0", "10.255.255.255")
|
|
|
|
privateRanges.addRange("172.16.0.0", "172.31.255.255")
|
|
|
|
privateRanges.addRange("192.168.0.0", "192.168.255.255")
|
|
|
|
return privateRanges.check(address, "ipv4")
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @return {string|undefined} */
|
|
|
|
function privateIpv4Address() {
|
|
|
|
return Object.values(os.networkInterfaces())
|
2024-07-30 12:07:58 +02:00
|
|
|
.map((net) => net.find((a) => a.family === "IPv4"))
|
|
|
|
.filter(Boolean)
|
|
|
|
.filter((net) => !net.internal && isPrivateIpv4Address(net.address))[0]?.address
|
2024-07-18 18:36:26 +02:00
|
|
|
}
|