2023-04-20 17:14:30 +02:00
|
|
|
import path from "node:path"
|
2021-11-05 17:11:35 +01:00
|
|
|
import fs from "fs-extra"
|
2022-12-27 15:37:40 +01:00
|
|
|
import { build as esbuild } from "esbuild"
|
2023-04-19 13:58:49 +02:00
|
|
|
import { getTutanotaAppVersion, runStep, writeFile } from "./buildUtils.js"
|
2022-05-10 10:58:11 +02:00
|
|
|
import "zx/globals"
|
2022-05-02 17:17:33 +02:00
|
|
|
import * as env from "./env.js"
|
2024-11-19 15:42:39 +01:00
|
|
|
import { externalTranslationsPlugin, libDeps, mimimiNativePlugin, preludeEnvPlugin, sqliteNativePlugin } from "./esbuildUtils.js"
|
2023-04-20 17:14:30 +02:00
|
|
|
import { fileURLToPath } from "node:url"
|
2022-05-10 16:36:19 +02:00
|
|
|
import * as LaunchHtml from "./LaunchHtml.js"
|
2023-04-20 17:14:30 +02:00
|
|
|
import os from "node:os"
|
2022-12-27 15:37:40 +01:00
|
|
|
import { checkOfflineDatabaseMigrations } from "./checkOfflineDbMigratons.js"
|
2023-02-23 18:09:24 +01:00
|
|
|
import { buildRuntimePackages } from "./packageBuilderFunctions.js"
|
2023-09-26 18:03:30 +02:00
|
|
|
import { domainConfigs } from "./DomainConfigs.js"
|
2023-04-19 13:58:49 +02:00
|
|
|
import { sh } from "./sh.js"
|
2021-11-05 17:11:35 +01:00
|
|
|
|
2024-07-29 11:33:23 +02:00
|
|
|
export async function runDevBuild({ stage, host, desktop, clean, ignoreMigrations, app }) {
|
|
|
|
|
const isCalendarBuild = app === "calendar"
|
|
|
|
|
const tsConfig = isCalendarBuild ? "tsconfig-calendar-app.json" : "tsconfig.json"
|
|
|
|
|
const buildDir = isCalendarBuild ? "build-calendar-app" : "build"
|
|
|
|
|
|
|
|
|
|
console.log("Building dev for", app)
|
|
|
|
|
|
2021-11-05 17:11:35 +01:00
|
|
|
if (clean) {
|
2022-05-02 17:17:33 +02:00
|
|
|
await runStep("Clean", async () => {
|
2024-07-29 11:33:23 +02:00
|
|
|
await fs.emptyDir(buildDir)
|
2022-05-02 17:17:33 +02:00
|
|
|
})
|
2021-11-05 17:11:35 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-07 09:57:44 +02:00
|
|
|
await runStep("Validate", () => {
|
|
|
|
|
if (ignoreMigrations) {
|
|
|
|
|
console.warn("CAUTION: Offline migrations are not being validated.")
|
|
|
|
|
} else {
|
|
|
|
|
checkOfflineDatabaseMigrations()
|
|
|
|
|
}
|
|
|
|
|
})
|
2022-05-17 17:40:44 +02:00
|
|
|
|
2022-05-02 17:17:33 +02:00
|
|
|
await runStep("Packages", async () => {
|
2023-02-23 18:09:24 +01:00
|
|
|
await buildRuntimePackages()
|
2022-05-02 17:17:33 +02:00
|
|
|
})
|
2022-02-14 10:19:20 +01:00
|
|
|
|
2023-04-19 13:58:49 +02:00
|
|
|
const version = await getTutanotaAppVersion()
|
2022-02-18 09:12:05 +01:00
|
|
|
|
2022-05-02 17:17:33 +02:00
|
|
|
await runStep("Types", async () => {
|
2024-07-29 11:33:23 +02:00
|
|
|
await sh`npx tsc --project ${tsConfig} --incremental ${true} --noEmit true`
|
2024-07-02 16:36:14 +02:00
|
|
|
})
|
|
|
|
|
|
2024-07-05 19:02:03 +02:00
|
|
|
/**
|
|
|
|
|
* @param host {string|null}
|
|
|
|
|
* @return {DomainConfigMap}
|
|
|
|
|
*/
|
2024-03-11 16:25:43 +01:00
|
|
|
function updateDomainConfigForHostname(host) {
|
|
|
|
|
if (host == null) {
|
|
|
|
|
return { ...domainConfigs }
|
|
|
|
|
} else {
|
|
|
|
|
const url = new URL(host)
|
|
|
|
|
const { protocol, hostname, port } = url
|
|
|
|
|
return {
|
|
|
|
|
...domainConfigs,
|
|
|
|
|
[url.hostname]: {
|
|
|
|
|
firstPartyDomain: true,
|
|
|
|
|
partneredDomainTransitionUrl: `${protocol}//${hostname}:${port}`,
|
|
|
|
|
apiUrl: `${protocol}//${hostname}:${port}`,
|
|
|
|
|
paymentUrl: `${protocol}//${hostname}:${port}/braintree.html`,
|
|
|
|
|
webauthnUrl: `${protocol}//${hostname}:${port}/webauthn`,
|
|
|
|
|
legacyWebauthnUrl: `${protocol}//${hostname}:${port}/webauthn`,
|
|
|
|
|
webauthnMobileUrl: `${protocol}//${hostname}:${port}/webauthnmobile`,
|
|
|
|
|
legacyWebauthnMobileUrl: `${protocol}//${hostname}:${port}/webauthnmobile`,
|
|
|
|
|
webauthnRpId: `${hostname}:${port}`,
|
|
|
|
|
u2fAppId: `${protocol}//${hostname}:${port}/u2f-appid.json`,
|
|
|
|
|
giftCardBaseUrl: `${protocol}//${hostname}:${port}/giftcard`,
|
|
|
|
|
referralBaseUrl: `${protocol}//${hostname}:${port}/signup`,
|
|
|
|
|
websiteBaseUrl: "https://tuta.com",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const extendedDomainConfigs = updateDomainConfigForHostname(host)
|
|
|
|
|
|
2024-07-05 19:02:03 +02:00
|
|
|
await buildWebPart({ stage, host, version, domainConfigs: extendedDomainConfigs, app })
|
2022-01-07 15:58:30 +01:00
|
|
|
|
2022-05-02 17:17:33 +02:00
|
|
|
if (desktop) {
|
2024-07-29 11:33:23 +02:00
|
|
|
await buildDesktopPart({ version, app })
|
2021-11-05 17:11:35 +01:00
|
|
|
}
|
2022-05-02 17:17:33 +02:00
|
|
|
}
|
|
|
|
|
|
2024-03-11 16:25:43 +01:00
|
|
|
/**
|
2024-07-05 19:02:03 +02:00
|
|
|
* @param p {object}
|
|
|
|
|
* @param p.stage {string}
|
|
|
|
|
* @param p.host {string|null}
|
|
|
|
|
* @param p.version {string}
|
|
|
|
|
* @param p.domainConfigs {DomainConfigMap}
|
|
|
|
|
* @param p.app {string}
|
2024-03-11 16:25:43 +01:00
|
|
|
* @return {Promise<void>}
|
|
|
|
|
*/
|
2024-07-29 11:33:23 +02:00
|
|
|
async function buildWebPart({ stage, host, version, domainConfigs, app }) {
|
|
|
|
|
const isCalendarBuild = app === "calendar"
|
|
|
|
|
const tsConfig = isCalendarBuild ? "tsconfig-calendar-app.json" : "tsconfig.json"
|
|
|
|
|
const buildDir = isCalendarBuild ? "build-calendar-app" : "build"
|
|
|
|
|
const entryFile = isCalendarBuild ? "src/calendar-app/calendar-app.ts" : "src/mail-app/app.ts"
|
2024-08-20 18:03:03 +02:00
|
|
|
const workerFile = isCalendarBuild ? "src/calendar-app/workerUtils/worker/calendar-worker.ts" : "src/mail-app/workerUtils/worker/mail-worker.ts"
|
2024-07-29 11:33:23 +02:00
|
|
|
|
2022-05-02 17:17:33 +02:00
|
|
|
await runStep("Web: Assets", async () => {
|
2024-07-29 11:33:23 +02:00
|
|
|
await prepareAssets(stage, host, version, domainConfigs, buildDir)
|
2024-07-02 16:36:14 +02:00
|
|
|
await fs.promises.writeFile(
|
2024-07-29 11:33:23 +02:00
|
|
|
`${buildDir}/worker-bootstrap.js`,
|
2024-07-02 16:36:14 +02:00
|
|
|
`importScripts("./polyfill.js")
|
|
|
|
|
importScripts("./worker.js")
|
2022-12-27 15:37:40 +01:00
|
|
|
`,
|
|
|
|
|
)
|
2022-05-02 17:17:33 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await runStep("Web: Esbuild", async () => {
|
2024-05-27 16:55:38 +02:00
|
|
|
const { esbuildWasmLoader } = await import("@tutao/tuta-wasm-loader")
|
2022-05-11 11:43:28 +02:00
|
|
|
await esbuild({
|
2022-05-10 10:58:11 +02:00
|
|
|
// Using named entry points so that it outputs build/worker.js and not build/api/worker/worker.js
|
2024-08-15 16:11:44 +02:00
|
|
|
entryPoints: { app: entryFile, worker: workerFile },
|
2024-07-29 11:33:23 +02:00
|
|
|
outdir: `./${buildDir}/`,
|
|
|
|
|
tsconfig: tsConfig,
|
2024-07-02 16:36:14 +02:00
|
|
|
// Why bundle at the moment:
|
|
|
|
|
// - We need to include all the imports: everything in src + libs. We could use wildcard in the future.
|
|
|
|
|
// - We can't have imports or dynamic imports in the worker because we can't start it as a module because of Firefox.
|
|
|
|
|
// (see https://bugzilla.mozilla.org/show_bug.cgi?id=1247687)
|
|
|
|
|
// We can theoretically compile it separately but it will be slower and more confusing.
|
|
|
|
|
bundle: true,
|
|
|
|
|
format: "esm",
|
|
|
|
|
// "both" is the most reliable as in Worker or on Android linked source maps don't work
|
|
|
|
|
sourcemap: "both",
|
|
|
|
|
define: {
|
|
|
|
|
// See Env.ts for explanation
|
|
|
|
|
NO_THREAD_ASSERTIONS: "true",
|
|
|
|
|
},
|
|
|
|
|
plugins: [
|
|
|
|
|
libDeps(),
|
|
|
|
|
externalTranslationsPlugin(),
|
|
|
|
|
esbuildWasmLoader({
|
2024-07-29 11:33:23 +02:00
|
|
|
output: `${process.cwd()}/${buildDir}/wasm`,
|
2024-07-02 16:36:14 +02:00
|
|
|
webassemblyLibraries: [
|
|
|
|
|
{
|
|
|
|
|
name: "liboqs.wasm",
|
|
|
|
|
command: "make -f Makefile_liboqs build",
|
2024-07-03 11:08:01 +02:00
|
|
|
workingDir: `${process.cwd()}/libs/webassembly/`,
|
|
|
|
|
env: {
|
2024-07-29 11:33:23 +02:00
|
|
|
WASM: `${process.cwd()}/${buildDir}/wasm/liboqs.wasm`,
|
2024-07-02 16:36:14 +02:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "argon2.wasm",
|
|
|
|
|
command: "make -f Makefile_argon2 build",
|
2024-07-03 11:08:01 +02:00
|
|
|
workingDir: `${process.cwd()}/libs/webassembly/`,
|
|
|
|
|
env: {
|
2024-07-29 11:33:23 +02:00
|
|
|
WASM: `${process.cwd()}/${buildDir}/wasm/argon2.wasm`,
|
2024-07-02 16:36:14 +02:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
})
|
|
|
|
|
})
|
2022-05-02 17:17:33 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-29 11:33:23 +02:00
|
|
|
async function buildDesktopPart({ version, app }) {
|
|
|
|
|
const isCalendarBuild = app === "calendar"
|
|
|
|
|
const buildDir = isCalendarBuild ? "build-calendar-app" : "build"
|
|
|
|
|
|
2022-05-02 17:17:33 +02:00
|
|
|
await runStep("Desktop: Esbuild", async () => {
|
2022-05-11 11:43:28 +02:00
|
|
|
await esbuild({
|
2024-07-05 11:32:43 +02:00
|
|
|
entryPoints: ["src/common/desktop/DesktopMain.ts", "src/common/desktop/sqlworker.ts"],
|
2024-07-29 11:33:23 +02:00
|
|
|
outdir: `./${buildDir}/desktop`,
|
2022-05-10 10:58:11 +02:00
|
|
|
// Why we bundle at the moment:
|
|
|
|
|
// - We need to include all the imports: we currently use some node_modules directly, without pre-bundling them like rest of libs we can't avoid it
|
2022-05-02 17:17:33 +02:00
|
|
|
bundle: true,
|
2022-12-27 15:37:40 +01:00
|
|
|
format: "cjs",
|
2022-05-02 17:17:33 +02:00
|
|
|
sourcemap: "linked",
|
|
|
|
|
platform: "node",
|
2024-11-19 15:42:39 +01:00
|
|
|
external: ["electron", "*.node"],
|
2024-03-11 16:25:43 +01:00
|
|
|
banner: {
|
|
|
|
|
js: `globalThis.buildOptions = globalThis.buildOptions ?? {}
|
|
|
|
|
globalThis.buildOptions.sqliteNativePath = "./better-sqlite3.node";`,
|
|
|
|
|
},
|
2022-05-02 17:17:33 +02:00
|
|
|
plugins: [
|
|
|
|
|
libDeps(),
|
|
|
|
|
sqliteNativePlugin({
|
|
|
|
|
environment: "electron",
|
2024-07-29 11:33:23 +02:00
|
|
|
dstPath: `./${buildDir}/desktop/better_sqlite3.node`,
|
2022-05-02 17:17:33 +02:00
|
|
|
platform: process.platform,
|
2023-11-20 17:47:22 +01:00
|
|
|
architecture: process.arch,
|
2023-09-13 11:28:25 +02:00
|
|
|
nativeBindingPath: "./better_sqlite3.node",
|
2022-05-02 17:17:33 +02:00
|
|
|
}),
|
2024-11-19 15:42:39 +01:00
|
|
|
mimimiNativePlugin({
|
|
|
|
|
dstPath: `./${buildDir}/desktop/`,
|
|
|
|
|
platform: process.platform,
|
|
|
|
|
}),
|
2023-09-26 18:03:30 +02:00
|
|
|
preludeEnvPlugin(env.create({ staticUrl: null, version, mode: "Desktop", dist: false, domainConfigs })),
|
2022-05-10 10:58:11 +02:00
|
|
|
externalTranslationsPlugin(),
|
2022-05-02 17:17:33 +02:00
|
|
|
],
|
|
|
|
|
})
|
|
|
|
|
})
|
2021-11-05 17:11:35 +01:00
|
|
|
|
2022-05-02 17:17:33 +02:00
|
|
|
await runStep("Desktop: assets", async () => {
|
|
|
|
|
const desktopIconsPath = "./resources/desktop-icons"
|
2024-07-29 11:33:23 +02:00
|
|
|
await fs.copy(desktopIconsPath, `./${buildDir}/desktop/resources/icons`, { overwrite: true })
|
2024-06-28 16:18:40 +02:00
|
|
|
await fs.move(`./${buildDir}/desktop/resources/icons/logo-solo-dev.png`, `./${buildDir}/desktop/resources/icons/logo-solo-red.png`, { overwrite: true })
|
|
|
|
|
await fs.move(`./${buildDir}/desktop/resources/icons/logo-solo-dev-small.png`, `./${buildDir}/desktop/resources/icons/logo-solo-red-small.png`, {
|
|
|
|
|
overwrite: true,
|
|
|
|
|
})
|
2022-12-27 15:37:40 +01:00
|
|
|
const templateGenerator = (await import("./electron-package-json-template.js")).default
|
2022-05-10 16:36:19 +02:00
|
|
|
const packageJSON = await templateGenerator({
|
2022-05-02 17:17:33 +02:00
|
|
|
nameSuffix: "-debug",
|
|
|
|
|
version,
|
2024-07-29 11:33:23 +02:00
|
|
|
updateUrl: `http://localhost:9000/client/${buildDir}`,
|
2022-05-02 17:17:33 +02:00
|
|
|
iconPath: path.join(desktopIconsPath, "logo-solo-red.png"),
|
2022-12-27 15:37:40 +01:00
|
|
|
sign: false,
|
2023-11-17 17:51:46 +01:00
|
|
|
architecture: "x64",
|
2022-05-02 17:17:33 +02:00
|
|
|
})
|
|
|
|
|
const content = JSON.stringify(packageJSON, null, 2)
|
|
|
|
|
|
2024-07-29 11:33:23 +02:00
|
|
|
await fs.createFile(`./${buildDir}/package.json`)
|
|
|
|
|
await fs.writeFile(`./${buildDir}/package.json`, content, "utf-8")
|
2022-05-02 17:17:33 +02:00
|
|
|
|
2024-07-29 11:33:23 +02:00
|
|
|
await fs.mkdir(`${buildDir}/desktop`, { recursive: true })
|
|
|
|
|
await fs.copyFile("src/common/desktop/preload.js", `${buildDir}/desktop/preload.js`)
|
|
|
|
|
await fs.copyFile("src/common/desktop/preload-webdialog.js", `${buildDir}/desktop/preload-webdialog.js`)
|
2022-05-02 17:17:33 +02:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-10 12:07:23 +02:00
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
|
|
|
const root = __dirname.split(path.sep).slice(0, -1).join(path.sep)
|
|
|
|
|
|
2024-07-29 11:33:23 +02:00
|
|
|
async function createBootstrap(env, buildDir) {
|
2022-05-10 12:07:23 +02:00
|
|
|
let jsFileName
|
|
|
|
|
let htmlFileName
|
|
|
|
|
switch (env.mode) {
|
|
|
|
|
case "App":
|
|
|
|
|
jsFileName = "index-app.js"
|
|
|
|
|
htmlFileName = "index-app.html"
|
|
|
|
|
break
|
|
|
|
|
case "Browser":
|
|
|
|
|
jsFileName = "index.js"
|
|
|
|
|
htmlFileName = "index.html"
|
|
|
|
|
break
|
|
|
|
|
case "Desktop":
|
|
|
|
|
jsFileName = "index-desktop.js"
|
|
|
|
|
htmlFileName = "index-desktop.html"
|
|
|
|
|
}
|
2022-12-27 15:37:40 +01:00
|
|
|
const imports = [{ src: "polyfill.js" }, { src: jsFileName }]
|
2022-05-10 12:07:23 +02:00
|
|
|
|
|
|
|
|
const template = `window.whitelabelCustomizations = null
|
|
|
|
|
window.env = ${JSON.stringify(env, null, 2)}
|
|
|
|
|
if (env.staticUrl == null && window.tutaoDefaultApiUrl) {
|
|
|
|
|
// overriden by js dev server
|
|
|
|
|
window.env.staticUrl = window.tutaoDefaultApiUrl
|
|
|
|
|
}
|
|
|
|
|
import('./app.js')`
|
2024-07-29 11:33:23 +02:00
|
|
|
await writeFile(`./${buildDir}/${jsFileName}`, template)
|
2022-05-10 12:07:23 +02:00
|
|
|
const html = await LaunchHtml.renderHtml(imports, env)
|
2024-07-29 11:33:23 +02:00
|
|
|
await writeFile(`./${buildDir}/${htmlFileName}`, html)
|
2022-05-02 17:17:33 +02:00
|
|
|
}
|
|
|
|
|
|
2022-05-10 12:07:23 +02:00
|
|
|
function getStaticUrl(stage, mode, host) {
|
|
|
|
|
if (stage === "local" && mode === "Browser") {
|
|
|
|
|
// We would like to use web app build for both JS server and actual server. For that we should avoid hardcoding URL as server
|
|
|
|
|
// might be running as one of testing HTTPS domains. So instead we override URL when the app is served from JS server
|
|
|
|
|
// (see DevServer).
|
|
|
|
|
// This is only relevant for browser environment.
|
|
|
|
|
return null
|
2022-12-27 15:37:40 +01:00
|
|
|
} else if (stage === "test") {
|
2023-10-19 13:33:29 +02:00
|
|
|
return "https://app.test.tuta.com"
|
2022-12-27 15:37:40 +01:00
|
|
|
} else if (stage === "prod") {
|
2023-09-25 13:28:09 +02:00
|
|
|
return "https://app.tuta.com"
|
2022-12-27 15:37:40 +01:00
|
|
|
} else if (stage === "local") {
|
2022-05-10 12:07:23 +02:00
|
|
|
return "http://" + os.hostname() + ":9000"
|
2022-12-27 15:37:40 +01:00
|
|
|
} else {
|
|
|
|
|
// host
|
2022-05-10 12:07:23 +02:00
|
|
|
return host
|
2022-01-12 14:43:01 +01:00
|
|
|
}
|
2022-05-10 10:58:11 +02:00
|
|
|
}
|
|
|
|
|
|
2024-03-11 16:25:43 +01:00
|
|
|
/**
|
|
|
|
|
* @param stage {string}
|
|
|
|
|
* @param host {string|null}
|
|
|
|
|
* @param version {string}
|
|
|
|
|
* @param domainConfigs {DomainConfigMap}
|
2024-07-29 11:33:23 +02:00
|
|
|
* @param buildDir {string}
|
2024-03-11 16:25:43 +01:00
|
|
|
* @return {Promise<void>}
|
|
|
|
|
*/
|
2024-07-29 11:33:23 +02:00
|
|
|
export async function prepareAssets(stage, host, version, domainConfigs, buildDir) {
|
2022-05-10 12:07:23 +02:00
|
|
|
await Promise.all([
|
2024-07-29 11:33:23 +02:00
|
|
|
await fs.emptyDir(path.join(root, `${buildDir}/images`)),
|
|
|
|
|
fs.copy(path.join(root, "/resources/favicon"), path.join(root, `/${buildDir}/images`)),
|
|
|
|
|
fs.copy(path.join(root, "/resources/images/"), path.join(root, `/${buildDir}/images`)),
|
|
|
|
|
fs.copy(path.join(root, "/resources/pdf/"), path.join(root, `/${buildDir}/pdf`)),
|
|
|
|
|
fs.copy(path.join(root, "/resources/desktop-icons"), path.join(root, `/${buildDir}/icons`)),
|
|
|
|
|
fs.copy(path.join(root, "/resources/wordlibrary.json"), path.join(root, `${buildDir}/wordlibrary.json`)),
|
|
|
|
|
fs.copy(path.join(root, "/src/braintree.html"), path.join(root, `${buildDir}/braintree.html`)),
|
2024-07-02 16:36:14 +02:00
|
|
|
])
|
|
|
|
|
|
2022-05-10 12:07:23 +02:00
|
|
|
// write empty file
|
2024-07-29 11:33:23 +02:00
|
|
|
await fs.writeFile(`${buildDir}/polyfill.js`, "")
|
2024-07-02 16:36:14 +02:00
|
|
|
|
2024-07-05 19:02:03 +02:00
|
|
|
/** @type {EnvMode[]} */
|
|
|
|
|
const modes = ["Browser", "App", "Desktop"]
|
|
|
|
|
for (const mode of modes) {
|
2024-07-29 11:33:23 +02:00
|
|
|
await createBootstrap(env.create({ staticUrl: getStaticUrl(stage, mode, host), version, mode, dist: false, domainConfigs }), buildDir)
|
2022-05-10 10:58:11 +02:00
|
|
|
}
|
2022-12-27 15:37:40 +01:00
|
|
|
}
|