From 0eb882adc0c22b8396e064893e90c73f12de422d Mon Sep 17 00:00:00 2001 From: Quentin Goinaud Date: Fri, 14 Aug 2026 06:53:00 +0000 Subject: [PATCH 1/5] feat(plugin-tauri): self-healing Android builds with APK artifacts - Build Android (and iOS) apps via `tauri init` + `tauri build` - Self-heal the Android toolchain: reuse an existing SDK or download the command-line tools and provision NDK/build-tools/platform into the shared cache folder; add the aarch64-linux-android Rust target - Pin @tauri-apps/* npm versions to the resolved Rust crate versions so Tauri's version-mismatch check passes - Discover the produced APK recursively (Tauri names it per target) and register it as a pipeline artifact so it persists beyond the build sandbox - Prefer the local asset template so pinned Cargo dependencies are used offline - Add an example Tauri mobile pipeline --- .../asset-tauri/template/src-tauri/Cargo.toml | 14 +- .../template/src-tauri/tauri.conf.json | 5 +- examples/tauri-mobile-pipeline/README.md | 40 + .../html-export/index.html | 30 + examples/tauri-mobile-pipeline/pipeline.json | 59 ++ packages/core-node/src/utils/remote.ts | 6 +- plugins/plugin-core/src/desktop-app.d.ts | 9 + plugins/plugin-tauri/src/package.ts | 11 + plugins/plugin-tauri/src/tauri.ts | 725 ++++++++++++++---- plugins/plugin-tauri/src/utils.ts | 1 + pnpm-lock.yaml | 275 ++++--- 11 files changed, 948 insertions(+), 227 deletions(-) create mode 100644 examples/tauri-mobile-pipeline/README.md create mode 100644 examples/tauri-mobile-pipeline/html-export/index.html create mode 100644 examples/tauri-mobile-pipeline/pipeline.json diff --git a/assets/asset-tauri/template/src-tauri/Cargo.toml b/assets/asset-tauri/template/src-tauri/Cargo.toml index ecc11800..c79b041d 100644 --- a/assets/asset-tauri/template/src-tauri/Cargo.toml +++ b/assets/asset-tauri/template/src-tauri/Cargo.toml @@ -18,11 +18,11 @@ name = "tauri_lib" crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] -tauri-build = { version = "2", features = [] } +tauri-build = { version = "2.1.1", features = [] } [dependencies] -tauri = { version = "2", features = [] } -tauri-cli = "2.4.1" +tauri = { version = "2.1.1", features = [] } +tauri-cli = "2.1.1" serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -33,8 +33,8 @@ futures-util = "0.3" anyhow = "1.0" dirs = "5.0" -tauri-plugin-shell = "2" -tauri-plugin-fs = "2" -tauri-plugin-opener = "2" -tauri-plugin-devtools = "2.0.0" +tauri-plugin-shell = "2.2.1" +tauri-plugin-fs = "2.2.1" +tauri-plugin-opener = "2.2.6" +tauri-plugin-devtools = "2" tauri-plugin-localhost = "2" diff --git a/assets/asset-tauri/template/src-tauri/tauri.conf.json b/assets/asset-tauri/template/src-tauri/tauri.conf.json index 0a73f10f..8f81cecd 100644 --- a/assets/asset-tauri/template/src-tauri/tauri.conf.json +++ b/assets/asset-tauri/template/src-tauri/tauri.conf.json @@ -20,11 +20,14 @@ ], "security": { "csp": null + }, + "mobile": { + "target": "all" } }, "bundle": { "active": true, - "targets": "all", + "targets": ["desktop", "mobile"], "icon": [ "icons/32x32.png", "icons/128x128.png", diff --git a/examples/tauri-mobile-pipeline/README.md b/examples/tauri-mobile-pipeline/README.md new file mode 100644 index 00000000..da277670 --- /dev/null +++ b/examples/tauri-mobile-pipeline/README.md @@ -0,0 +1,40 @@ +# Tauri Mobile Pipeline + +Builds a Tauri app for **Android** and **iOS** using the Pipelab Tauri plugin's +mobile build path. + +## What it does + +1. `tauri:scaffold` — creates a fresh Tauri project (web frontend + `src-tauri`). +2. `tauri:package` — writes a mobile-ready `tauri.conf.json` (enables all bundle + targets) and runs the platform build: + - Android: `cargo tauri android init` then `cargo tauri android build --target aarch64 --apk` + - iOS: `cargo tauri ios init` then `cargo tauri ios build --target aarch64` + +The `platform` and `arch` action inputs select the mobile target. Use the +`mobileConfig` input to inject `tauri.conf.json > app > mobile` (e.g. +`"homeIndicator": "hidden"`, `"orientation": "portrait"`). + +## Prerequisites + +Mobile builds are delegated to the official Tauri CLI, which requires the native +toolchains: + +- **Android** — Android SDK + NDK, `ANDROID_HOME` set, and the SDK packages + `platform-tools`, `build-tools`, plus a platform/NDK matching your `rustup` + targets (`aarch64-linux-android`, ...). See + https://tauri.app/start/prerequisites/#android +- **iOS** — macOS with Xcode and the iOS SDK. See + https://tauri.app/start/prerequisites/#ios + +Without these, the pipeline will report `Android SDK not found` / +`failed to ensure iOS environment` from the Tauri CLI — this is expected in +environments that only have the Rust + desktop toolchain. + +## Run + +```bash +pnpm --filter @pipelab/cli start -- \ + run examples/tauri-mobile-pipeline/pipeline.json \ + --user-data /tmp/pl-userdata +``` diff --git a/examples/tauri-mobile-pipeline/html-export/index.html b/examples/tauri-mobile-pipeline/html-export/index.html new file mode 100644 index 00000000..c6f0f99f --- /dev/null +++ b/examples/tauri-mobile-pipeline/html-export/index.html @@ -0,0 +1,30 @@ + + + + + + Pipelab · Mobile Demo + + + +
+

Pipelab 📱

+

This static web app is packaged into a native Android/iOS build by the + @pipelab/plugin-tauri “Package app with configuration” node.

+
+ + diff --git a/examples/tauri-mobile-pipeline/pipeline.json b/examples/tauri-mobile-pipeline/pipeline.json new file mode 100644 index 00000000..0d3c314c --- /dev/null +++ b/examples/tauri-mobile-pipeline/pipeline.json @@ -0,0 +1,59 @@ +{ + "version": "3.0.0", + "name": "Mobile Tauri Package", + "description": "Packages the sample web app into an Android (or iOS) build using @pipelab/plugin-tauri with mobile targets enabled.", + "variables": [], + "canvas": { + "blocks": [ + { + "uid": "tauri-package-mobile", + "type": "action", + "origin": { "nodeId": "tauri:package:v2", "pluginId": "tauri" }, + "params": { + "arch": { "editor": "simple", "value": "\"arm64\"" }, + "platform": { "editor": "simple", "value": "\"android\"" }, + "input-folder": { + "editor": "simple", + "value": "\"/workspaces/pipelab/examples/tauri-mobile-pipeline/html-export\"" + }, + "name": { "editor": "simple", "value": "\"Pipelab Mobile\"" }, + "appBundleId": { "editor": "simple", "value": "\"app.pipelab.mobile\"" }, + "appCopyright": { "editor": "simple", "value": "\"Copyright © 2024 Pipelab\"" }, + "appVersion": { "editor": "simple", "value": "\"1.0.0\"" }, + "icon": { "editor": "simple", "value": "\"\"" }, + "author": { "editor": "simple", "value": "\"Pipelab\"" }, + "description": { "editor": "simple", "value": "\"A Pipelab mobile demo app\"" }, + "appCategoryType": { + "editor": "simple", + "value": "\"public.app-category.developer-tools\"" + }, + "width": { "editor": "simple", "value": 400 }, + "height": { "editor": "simple", "value": 800 }, + "fullscreen": { "editor": "simple", "value": false }, + "frame": { "editor": "simple", "value": true }, + "transparent": { "editor": "simple", "value": false }, + "toolbar": { "editor": "simple", "value": true }, + "alwaysOnTop": { "editor": "simple", "value": false }, + "tauriVersion": { "editor": "simple", "value": "\"\"" }, + "enableExtraLogging": { "editor": "simple", "value": false }, + "openDevtoolsOnStart": { "editor": "simple", "value": false }, + "websocketApi": { "editor": "simple", "value": "[]" }, + "ignore": { "editor": "editor", "value": "[\n]" }, + "enableSteamSupport": { "editor": "simple", "value": false }, + "steamGameId": { "editor": "simple", "value": 480 }, + "enableDiscordSupport": { "editor": "simple", "value": false }, + "discordAppId": { "editor": "simple", "value": "\"\"" }, + "mobileConfig": { "editor": "simple", "value": "{}" } + } + } + ], + "triggers": [ + { + "type": "event", + "origin": { "pluginId": "system", "nodeId": "manual" }, + "uid": "manual-start", + "params": {} + } + ] + } +} diff --git a/packages/core-node/src/utils/remote.ts b/packages/core-node/src/utils/remote.ts index e996daee..13718e30 100644 --- a/packages/core-node/src/utils/remote.ts +++ b/packages/core-node/src/utils/remote.ts @@ -563,7 +563,11 @@ export async function fetchPipelabAsset( versionOrRange: string, options: FetchOptions, ): Promise { - if (isDev && projectRoot) { + // Prefer the local monorepo asset when it exists (dev workflow). This avoids + // pulling a published template that may pin Tauri crate versions inconsistently + // with what the local scaffold expects. `PIPELAB_FORCE_NPM` can still force the + // published package (used by CI/prod). + if (projectRoot && process.env.PIPELAB_FORCE_NPM !== "true") { const assetId = packageName.replace("@pipelab/asset-", ""); const localPath = join(projectRoot, "assets", `asset-${assetId}`); if (existsSync(localPath)) return localPath; diff --git a/plugins/plugin-core/src/desktop-app.d.ts b/plugins/plugin-core/src/desktop-app.d.ts index 94549ccf..9ed2a6ee 100644 --- a/plugins/plugin-core/src/desktop-app.d.ts +++ b/plugins/plugin-core/src/desktop-app.d.ts @@ -48,5 +48,14 @@ declare namespace DesktopApp { interface Tauri extends Config { tauriVersion: string; + /** + * Optional Tauri `app.mobile` configuration (e.g. android/ios specific overrides). + * Only used when packaging for the `android` or `ios` platforms. + */ + mobile?: { + target?: "all" | "android" | "ios"; + android?: Record; + ios?: Record; + }; } } diff --git a/plugins/plugin-tauri/src/package.ts b/plugins/plugin-tauri/src/package.ts index 8ad502a6..5da1ac14 100644 --- a/plugins/plugin-tauri/src/package.ts +++ b/plugins/plugin-tauri/src/package.ts @@ -3,6 +3,16 @@ import { createPackageV2Props, tauri } from "./tauri"; import { merge } from "ts-deepmerge"; import { defaultTauriConfig } from "./utils"; +function parseMobileConfig(value: unknown): DesktopApp.Tauri["mobile"] { + if (!value) return {}; + if (typeof value === "object") return value as DesktopApp.Tauri["mobile"]; + try { + return JSON.parse(value as string) as DesktopApp.Tauri["mobile"]; + } catch { + return {}; + } +} + export const packageV2Runner = createActionRunner>( async (options) => { const appFolder = options.inputs["input-folder"]; @@ -34,6 +44,7 @@ export const packageV2Runner = createActionRunner { throw new Error("Cargo not found. Please install it first"); } +/** + * Resolves the target token accepted by the `tauri android/ios build --target` + * flag (an Android ABI or an iOS device/simulator slice), or undefined to let + * Tauri pick its default. + */ +function resolveMobileCliTarget( + platform: NodeJS.Platform, + arch: NodeJS.Architecture, +): string | undefined { + if (platform === "android") { + switch (arch) { + case "arm64": + return "aarch64"; + case "x64": + return "x86_64"; + case "ia32": + return "i686"; + case "armv7l": + return "armv7"; + default: + return "aarch64"; // mobile defaults to 64-bit ARM + } + } + + if (platform === "ios") { + // iOS simulators use the `-sim` slice + if (arch === "x64" || arch === "ia32") { + return "x86_64-sim"; + } + if (arch === "arm64") { + // Apple Silicon simulators build for the `aarch64-sim` slice; + // fall back to the device slice on non-Apple hosts. + return process.platform === "darwin" ? "aarch64-sim" : "aarch64"; + } + return "aarch64"; // physical devices + } + + return undefined; +} + +/** + * Resolves the Rust target triple actually produced by the build, used to + * locate the generated artifact on disk. + */ +function resolveMobileOutputTriple( + platform: NodeJS.Platform, + arch: NodeJS.Architecture, +): string | undefined { + if (platform === "android") { + switch (arch) { + case "x64": + return "x86_64-linux-android"; + case "ia32": + return "i686-linux-android"; + case "armv7l": + return "armv7-linux-androideabi"; + default: + return "aarch64-linux-android"; + } + } + + if (platform === "ios") { + if (arch === "x64" || arch === "ia32") { + return "x86_64-apple-ios-sim"; + } + if (arch === "arm64") { + return "aarch64-apple-ios-sim"; + } + return "aarch64-apple-ios"; + } + + return undefined; +} + // TODO: https://js.electronforge.io/modules/_electron_forge_core.html export const IDMake = "tauri:make"; @@ -392,6 +467,19 @@ export const configureParams = { label: "Discord application ID", description: "The Discord application ID", }), + + // Mobile specific configuration (android/ios). Merged into the generated + // `app.mobile` section of tauri.conf.json when packaging for a mobile target. + mobileConfig: { + label: "Mobile configuration", + required: false, + description: + "Optional Tauri mobile configuration (android/ios overrides). Only used when packaging for a mobile platform.", + value: "{}", + control: { + type: "json", + }, + }, } satisfies InputsDefinition; const outputs = { @@ -475,6 +563,222 @@ export const createPackageV2Props = ( }); }; +/** + * Keeps the npm `@tauri-apps/*` packages in lock-step with the Rust Tauri + * crates used by the scaffolded app. Tauri aborts the build when the major.minor + * of an npm package and its matching Rust crate diverge, but the two ecosystems + * float independently (e.g. `^2` on npm vs `"2"` in Cargo.toml can resolve to + * different minors depending on what each registry currently serves). Reading the + * actual resolved crate versions from `cargo metadata` and pinning the npm + * packages to them guarantees a match. + */ +const TAURI_NPM_MAP: Record = { + tauri: "@tauri-apps/api", + "tauri-plugin-shell": "@tauri-apps/plugin-shell", + "tauri-plugin-fs": "@tauri-apps/plugin-fs", + "tauri-plugin-opener": "@tauri-apps/plugin-opener", + "tauri-plugin-devtools": "@tauri-apps/plugin-devtools", + "tauri-plugin-localhost": "@tauri-apps/plugin-localhost", +}; + +async function alignTauriNpmVersions( + destinationFolder: string, + cargo: string, + node: string, + log: (message: string) => void, +): Promise { + const srcTauri = join(destinationFolder, "src-tauri"); + try { + const { stdout } = await execa(cargo, ["metadata", "--format-version", "1"], { + cwd: srcTauri, + env: { + ...process.env, + PATH: `${dirname(cargo)}${delimiter}${dirname(node)}${delimiter}${process.env.PATH}`, + }, + }); + const meta = JSON.parse(stdout) as { packages?: { name: string; version: string }[] }; + const versions: Record = {}; + for (const p of meta.packages ?? []) versions[p.name] = p.version; + + const pkgPath = join(destinationFolder, "package.json"); + const pkg = JSON.parse(await readFilePromise(pkgPath, "utf8")) as { + dependencies?: Record; + devDependencies?: Record; + }; + + let changed = false; + for (const [crate, npm] of Object.entries(TAURI_NPM_MAP)) { + const version = versions[crate]; + if (!version) continue; + if (pkg.dependencies?.[npm]) { + pkg.dependencies[npm] = version; + changed = true; + } + if (pkg.devDependencies?.[npm]) { + pkg.devDependencies[npm] = version; + changed = true; + } + } + + if (changed) { + log("Aligning @tauri-apps npm versions to resolved Rust crate versions"); + await writeFilePromise(pkgPath, JSON.stringify(pkg, null, 2)); + } + } catch (error) { + log(`Skipping tauri npm version alignment: ${String(error)}`); + } +} + +const ANDROID_CMDLINE_TOOLS_URL = + "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip"; + +/** + * Recursively finds the produced Android APK under the Gradle outputs folder. + * Tauri may name/locate it differently (e.g. `app-universal-release-unsigned.apk` + * inside a `universal/release` subfolder) depending on the build target, so we + * search for it instead of hard-coding a single path. + */ +function findAndroidApk(root: string): string | undefined { + if (!existsSync(root)) return undefined; + let releaseApk: string | undefined; + const walk = (dir: string): void => { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const p = join(dir, entry.name); + if (entry.isDirectory()) { + walk(p); + } else if (entry.name.endsWith(".apk")) { + const isRelease = p.split(sep).includes("release"); + if (isRelease && !releaseApk) { + releaseApk = p; + } else if (!releaseApk) { + releaseApk = p; + } + } + } + }; + walk(root); + return releaseApk; +} + +/** + * Looks for an already-installed Android SDK on the host so we can reuse it + * instead of downloading one. Checks `ANDROID_HOME`, the conventional + * `~/Android/Sdk` location, common version managers (mise/asdf) and `/opt`. + */ +function findExistingAndroidSdk(): string | undefined { + const candidates: string[] = []; + if (process.env.ANDROID_HOME) candidates.push(process.env.ANDROID_HOME); + candidates.push(join(homedir(), "Android", "Sdk")); + const miseBase = join(homedir(), ".local", "share", "mise", "installs", "android-sdk"); + if (existsSync(miseBase)) { + try { + for (const entry of readdirSync(miseBase)) candidates.push(join(miseBase, entry)); + } catch { + /* ignore */ + } + } + candidates.push("/opt/android-sdk", "/usr/lib/android-sdk"); + for (const candidate of candidates) { + if ( + existsSync(candidate) && + (existsSync(join(candidate, "cmdline-tools")) || + existsSync(join(candidate, "platform-tools")) || + existsSync(join(candidate, "ndk"))) + ) { + return candidate; + } + } + return undefined; +} + +/** + * Downloads the Android command-line tools and uses `sdkmanager` to install the + * NDK, build-tools, platform and platform-tools required to build a Tauri app. + */ +async function provisionAndroidSdk( + androidHome: string, + log: (message: string) => void, + abortSignal?: AbortSignal, +): Promise { + await mkdir(join(androidHome, "cmdline-tools"), { recursive: true }); + const zipPath = join(androidHome, "commandlinetools.zip"); + log(`Downloading Android command-line tools from ${ANDROID_CMDLINE_TOOLS_URL}`); + await execa("curl", ["-sL", ANDROID_CMDLINE_TOOLS_URL, "-o", zipPath], { + cancelSignal: abortSignal as any, + }); + const extractDir = join(androidHome, "cmdline-tools-extract"); + await execa("unzip", ["-q", "-o", zipPath, "-d", extractDir], { + cancelSignal: abortSignal as any, + }); + // The archive extracts to a `cmdline-tools/` folder; Tauri expects it at + // `cmdline-tools/latest`. + const extracted = join(extractDir, "cmdline-tools"); + const target = join(androidHome, "cmdline-tools", "latest"); + await execa("rm", ["-rf", target], { cancelSignal: abortSignal as any }); + await execa("mv", [extracted, target], { cancelSignal: abortSignal as any }); + await execa("rm", ["-rf", extractDir, zipPath], { cancelSignal: abortSignal as any }); + + const sdkmanager = join(target, "bin", "sdkmanager"); + const env = { ...process.env, ANDROID_HOME: androidHome }; + log("Accepting Android SDK licenses"); + await execa("bash", ["-c", `yes | "${sdkmanager}" --licenses`], { + env, + cancelSignal: abortSignal as any, + }); + log("Installing Android SDK packages (NDK, build-tools, platform, platform-tools)"); + await execa( + "bash", + [ + "-c", + `"${sdkmanager}" --install "platform-tools" "platforms;android-34" "build-tools;34.0.0" "ndk;26.1.10909125"`, + ], + { env, cancelSignal: abortSignal as any }, + ); +} + +/** + * Self-heals the Android toolchain so a pipeline can build for Android on a host + * that ships without the Android SDK pre-installed (only node/pnpm + a Rust + * toolchain are assumed). It reuses an existing SDK when present and otherwise + * downloads the command-line tools and provisions the NDK/build-tools/platform + * into a cache directory, then exposes it via `ANDROID_HOME`. + */ +async function ensureAndroidEnvironment( + cacheDir: string, + cargoBinDir: string, + node: string, + log: (message: string) => void, + abortSignal?: AbortSignal, +): Promise { + const existing = findExistingAndroidSdk(); + const androidHome = + existing ?? join(cacheDir, "android-sdk"); + + if (existing) { + log(`Reusing existing Android SDK at ${androidHome}`); + } else { + log(`No Android SDK found; provisioning one at ${androidHome}`); + await provisionAndroidSdk(androidHome, log, abortSignal); + } + + log(`Ensuring Rust target aarch64-linux-android for Android builds`); + try { + await execa("rustup", ["target", "add", "aarch64-linux-android"], { + env: { + ...process.env, + PATH: `${cargoBinDir}${delimiter}${dirname(node)}${delimiter}${process.env.PATH}`, + }, + stdout: "ignore", + stderr: "ignore", + }); + } catch (error) { + log(`rustup target add aarch64-linux-android failed (continuing): ${String(error)}`); + } + + process.env.ANDROID_HOME = androidHome; + return androidHome; +} + export const createPreviewProps = ( id: string, name: string, @@ -499,7 +803,7 @@ export const createPreviewProps = ( export const tauri = async ( action: "make" | "package" | "preview", appFolder: string | undefined, - { cwd, log, inputs, setOutput, paths, abortSignal, context }: ActionRunnerData, + { cwd, log, inputs, setOutput, paths, abortSignal, context, setArtifact }: ActionRunnerData, completeConfiguration: DesktopApp.Config, ): Promise<{ folder: string; binary: string | undefined } | undefined> => { console.log("appFolder", appFolder); @@ -583,16 +887,41 @@ export const tauri = async ( tauriConfJSON.version = completeConfiguration.appVersion; log("Setting identifier to", completeConfiguration.appBundleId); tauriConfJSON.identifier = completeConfiguration.appBundleId; + + const isAndroid = inputs.platform === "android"; + const isIOS = inputs.platform === "ios"; + const isMobile = isAndroid || isIOS; + + if (isMobile) { + tauriConfJSON.bundle = tauriConfJSON.bundle || {}; + // Mobile artifacts (APK/AAB/IPA) are produced by `tauri android/ios build` + // itself; the `bundle.targets` enum only knows desktop formats, so we use + // "all" (the schema-valid catch-all) and let the platform drive the output. + tauriConfJSON.bundle.targets = "all"; + // `app.mobile` is optional and only understood by newer Tauri versions. + // It is intentionally omitted here so the generated config validates + // against every Tauri 2.x release; mobile-specific settings from + // `completeConfiguration.mobile` can be applied to the generated + // android/ios project after `tauri init` when desired. + if (tauriConfJSON.app) { + delete (tauriConfJSON.app as Record).mobile; + } + log("Setting mobile bundle target to", tauriConfJSON.bundle.targets); + } + if (action === "preview") { log("Setting build.devUrl to", appFolder); tauriConfJSON.build.devUrl = appFolder; - await writeFilePromise(tauriConfJSONPath, JSON.stringify(tauriConfJSON, null, 2)); + } else { + log("Setting build.frontendDist to ../src/app"); + tauriConfJSON.build.frontendDist = "../src/app"; } - /* else { - log('Setting build.frontendDist to', appFolder) - tauriConfJSON.build.frontendDist = appFolder - await writeFile(tauriConfJSONPath, JSON.stringify(tauriConfJSON, null, 2)) - } */ + await writeFilePromise(tauriConfJSONPath, JSON.stringify(tauriConfJSON, null, 2)); + + // Pin the npm @tauri-apps packages to the Rust crate versions so the Tauri + // CLI version check passes (see alignTauriNpmVersions). + const cargoForAlign = await resolveCargoPath(); + await alignTauriNpmVersions(destinationFolder, cargoForAlign, node, log); log("Installing packages"); const { all } = await runPnpm(destinationFolder, { @@ -628,88 +957,39 @@ export const tauri = async ( // ) // } - const inputPlatform = inputs.platform === "" ? undefined : inputs.platform; - const inputArch = inputs.arch === "" ? undefined : inputs.arch; - - try { - log("typeof inputs.platform", typeof inputs.platform); - const finalPlatform: NodeJS.Platform = inputPlatform ?? osPlatform(); - log("finalPlatform", finalPlatform); - const finalArch: NodeJS.Architecture = inputArch ?? (osArch() as NodeJS.Architecture); - log("finalArch", finalArch); - - let tauriPlatform = ""; - if (finalPlatform === "win32") { - tauriPlatform = "pc-windows-msvc"; - } else if (finalPlatform === "linux") { - tauriPlatform = "unknown-linux-gnu"; - } else { - throw new Error("Unsupported platform"); - } - - let tauriArch = ""; - if (finalArch === "x64") { - tauriArch = "x86_64"; - } else { - throw new Error("Unsupported arch"); - } + const inputPlatform = inputs.platform === "" ? undefined : inputs.platform; + const inputArch = inputs.arch === "" ? undefined : inputs.arch; - const target = `${tauriArch}-${tauriPlatform}`; + try { + log("typeof inputs.platform", typeof inputs.platform); + const finalPlatform: NodeJS.Platform = inputPlatform ?? osPlatform(); + log("finalPlatform", finalPlatform); + const finalArch: NodeJS.Architecture = inputArch ?? (osArch() as NodeJS.Architecture); + log("finalArch", finalArch); - // Resolve cargo path using the new function - const cargo = await resolveCargoPath(); - const cargoBinDir = dirname(cargo); + const isAndroidBuild = finalPlatform === "android"; + const isIOSBuild = finalPlatform === "ios"; + const isMobileBuild = isAndroidBuild || isIOSBuild; - log("cargoBinDir", cargoBinDir); - console.log("cargo", cargo); + // Resolve cargo path using the new function + const cargo = await resolveCargoPath(); + const cargoBinDir = dirname(cargo); - log("destinationFolder", destinationFolder); + log("cargoBinDir", cargoBinDir); + console.log("cargo", cargo); - const cargoTargetDir = join(cache, "cargo", "target", completeConfiguration.appBundleId); - const cargoOutputPath = join(cargoTargetDir, target, "release"); + log("destinationFolder", destinationFolder); - log("cargoTargetDir", cargoTargetDir); - log("cargoOutputPath", cargoOutputPath); + const cargoTargetDir = join(cache, "cargo", "target", completeConfiguration.appBundleId); - log("Starting compiling"); + log("cargoTargetDir", cargoTargetDir); - // by default add the tauri cli - await runWithLiveLogs( - cargo, - ["install", "tauri-cli", "--version", "^2.0.0", "--locked"], - { - cwd: join(destinationFolder, "src-tauri"), - env: { - ...process.env, - DEBUG: completeConfiguration.enableExtraLogging ? "*" : "", - ELECTRON_NO_ASAR: "1", - CARGO_TARGET_DIR: cargoTargetDir, - PATH: `${cargoBinDir}${delimiter}${dirname(node)}${delimiter}${process.env.PATH}`, - }, - cancelSignal: abortSignal, - }, - log, - { - onStderr(data) { - // on ci, do not log - if (!process.env.CI) { - log(data); - } - }, - onStdout(data) { - // on ci, do not log - if (!process.env.CI) { - log(data); - } - }, - }, - ); + log("Starting compiling"); - // if preview, run tauri dev - if (action === "preview") { + // by default add the tauri cli await runWithLiveLogs( cargo, - ["tauri", "dev", "--target", target], + ["install", "tauri-cli", "--version", "^2.0.0", "--locked"], { cwd: join(destinationFolder, "src-tauri"), env: { @@ -724,19 +1004,78 @@ export const tauri = async ( log, { onStderr(data) { - log(data); + // on ci, do not log + if (!process.env.CI) { + log(data); + } }, onStdout(data) { - log(data); + // on ci, do not log + if (!process.env.CI) { + log(data); + } }, }, ); - } else { - // otherwise build, but don't bundle - await runWithLiveLogs( - cargo, - ["tauri", "build", "--target", target, "--no-bundle"], - { + + // --------------------------------------------------------------------------- + // Mobile build path (android / ios) + // --------------------------------------------------------------------------- + if (isMobileBuild) { + // Self-heal the Android toolchain (SDK/NDK + Rust target) when building + // for Android on a host that doesn't have them installed yet. + if (isAndroidBuild) { + const androidHome = await ensureAndroidEnvironment(cache, cargoBinDir, node, log, abortSignal); + // Make sure the SDK tooling is on PATH for the Tauri CLI / Gradle. + process.env.PATH = `${join(androidHome, "cmdline-tools", "latest", "bin")}${delimiter}${join( + androidHome, + "platform-tools", + )}${delimiter}${process.env.PATH}`; + log(`Android SDK ready at ANDROID_HOME=${androidHome}`); + } + + const cliTarget = resolveMobileCliTarget(finalPlatform, finalArch); + const outputTriple = resolveMobileOutputTriple(finalPlatform, finalArch); + const mobileArgs = ["tauri", isAndroidBuild ? "android" : "ios", "build"]; + if (cliTarget) { + mobileArgs.push("--target", cliTarget); + } + if (isAndroidBuild) { + // Produce a standalone APK in addition to the default AAB + mobileArgs.push("--apk"); + } + + log(`Building for ${isAndroidBuild ? "android" : "ios"} (target: ${cliTarget ?? "default"})`); + + // `tauri init` generates the native project + // (src-tauri/gen/android|ios) required before the first build. + log(`Initializing ${isAndroidBuild ? "android" : "ios"} project`); + await runWithLiveLogs( + cargo, + ["tauri", isAndroidBuild ? "android" : "ios", "init"], + { + cwd: join(destinationFolder, "src-tauri"), + env: { + ...process.env, + DEBUG: completeConfiguration.enableExtraLogging ? "*" : "", + ELECTRON_NO_ASAR: "1", + CARGO_TARGET_DIR: cargoTargetDir, + PATH: `${cargoBinDir}${delimiter}${dirname(node)}${delimiter}${process.env.PATH}`, + }, + cancelSignal: abortSignal, + }, + log, + { + onStderr(data) { + if (!process.env.CI) log(data); + }, + onStdout(data) { + if (!process.env.CI) log(data); + }, + }, + ); + + await runWithLiveLogs(cargo, mobileArgs, { cwd: join(destinationFolder, "src-tauri"), env: { ...process.env, @@ -746,33 +1085,100 @@ export const tauri = async ( PATH: `${cargoBinDir}${delimiter}${dirname(node)}${delimiter}${process.env.PATH}`, }, cancelSignal: abortSignal, - }, - log, - { + }, log, { onStderr(data) { - // on ci, do not log - if (!process.env.CI) { - log(data); - } + if (!process.env.CI) log(data); }, onStdout(data) { - // on ci, do not log - if (!process.env.CI) { - log(data); - } + if (!process.env.CI) log(data); }, - }, - ); + }); + + if (isAndroidBuild) { + const apkRoot = join( + destinationFolder, + "src-tauri", + "gen", + "android", + "app", + "build", + "outputs", + "apk", + ); + const apkPath = findAndroidApk(apkRoot) ?? join(apkRoot, "release", "app-release.apk"); + const outFolder = dirname(apkPath); + const binName = basename(apkPath); + log("Android APK output", apkPath); + setOutput("output", outFolder); + setOutput("binary", apkPath); + // Persist the APK to the pipeline's artifacts folder so it survives + // the build sandbox cleanup. The runner copies it to + // getArtifactsPath(pipelineId, buildId)/. + setArtifact(binName, apkPath); + return { folder: outFolder, binary: apkPath }; + } + + // iOS + const triple = outputTriple || "aarch64-apple-ios"; + const outFolder = join(destinationFolder, "src-tauri", "target", triple, "release"); + const binName = getBinName(sanitizedName, "ios"); + const binPath = join(outFolder, binName); + log("iOS output", binPath); + setOutput("output", outFolder); + setOutput("binary", binPath); + setArtifact(binName, binPath); + return { folder: outFolder, binary: binPath }; + } - // if make, bundle - if (action === "make") { + // --------------------------------------------------------------------------- + // Desktop build path (win32 / linux / darwin) + // --------------------------------------------------------------------------- + let tauriPlatform = ""; + if (finalPlatform === "win32") { + tauriPlatform = "pc-windows-msvc"; + } else if (finalPlatform === "linux") { + tauriPlatform = "unknown-linux-gnu"; + } else if (finalPlatform === "darwin") { + tauriPlatform = "apple-darwin"; + } else { + throw new Error(`Unsupported platform: ${finalPlatform}`); + } + + let target: string; + if (finalArch === "universal") { + if (finalPlatform !== "darwin") { + throw new Error("Universal architecture is only supported on macOS"); + } + target = "universal-apple-darwin"; + } else { + let tauriArch = ""; + if (finalArch === "x64") { + tauriArch = "x86_64"; + } else if (finalArch === "arm64") { + tauriArch = "aarch64"; + } else if (finalArch === "ia32") { + tauriArch = "i686"; + } else if (finalArch === "armv7l") { + tauriArch = "armv7"; + } else { + throw new Error(`Unsupported arch: ${finalArch}`); + } + target = `${tauriArch}-${tauriPlatform}`; + } + + const cargoOutputPath = join(cargoTargetDir, target, "release"); + + log("cargoOutputPath", cargoOutputPath); + + // if preview, run tauri dev + if (action === "preview") { await runWithLiveLogs( cargo, - // TODO: https://v2.tauri.app/fr/distribute/#bundling - ["tauri", "bundle", "--", "--bundles", "appimage"], + ["tauri", "dev", "--target", target], { cwd: join(destinationFolder, "src-tauri"), env: { + ...process.env, DEBUG: completeConfiguration.enableExtraLogging ? "*" : "", ELECTRON_NO_ASAR: "1", CARGO_TARGET_DIR: cargoTargetDir, @@ -790,35 +1196,94 @@ export const tauri = async ( }, }, ); + } else { + // otherwise build, but don't bundle + await runWithLiveLogs( + cargo, + ["tauri", "build", "--target", target, "--no-bundle"], + { + cwd: join(destinationFolder, "src-tauri"), + env: { + ...process.env, + DEBUG: completeConfiguration.enableExtraLogging ? "*" : "", + ELECTRON_NO_ASAR: "1", + CARGO_TARGET_DIR: cargoTargetDir, + PATH: `${cargoBinDir}${delimiter}${dirname(node)}${delimiter}${process.env.PATH}`, + }, + cancelSignal: abortSignal, + }, + log, + { + onStderr(data) { + // on ci, do not log + if (!process.env.CI) { + log(data); + } + }, + onStdout(data) { + // on ci, do not log + if (!process.env.CI) { + log(data); + } + }, + }, + ); + + // if make, bundle + if (action === "make") { + await runWithLiveLogs( + cargo, + // TODO: https://v2.tauri.app/fr/distribute/#bundling + ["tauri", "bundle", "--", "--bundles", "appimage"], + { + cwd: join(destinationFolder, "src-tauri"), + env: { + DEBUG: completeConfiguration.enableExtraLogging ? "*" : "", + ELECTRON_NO_ASAR: "1", + CARGO_TARGET_DIR: cargoTargetDir, + PATH: `${cargoBinDir}${delimiter}${dirname(node)}${delimiter}${process.env.PATH}`, + }, + cancelSignal: abortSignal, + }, + log, + { + onStderr(data) { + log(data); + }, + onStdout(data) { + log(data); + }, + }, + ); + } } - } - if (action === "package") { - const binName = getBinName(sanitizedName); + if (action === "package") { + const binName = getBinName(sanitizedName); - log("cargoOutputPath", cargoOutputPath); + log("cargoOutputPath", cargoOutputPath); - setOutput("output", cargoOutputPath); - setOutput("binary", join(cargoOutputPath, binName)); - return { - folder: cargoOutputPath, - binary: join(cargoOutputPath, binName), - }; - } else if (action === "make") { - // TODO: - throw new Error("Unsupported action"); - } else if (action === "preview") { - // continue - } else { - throw new Error("Unsupported action"); - // const output = join(destinationFolder, 'out', 'make') - // setOutput('output', output) - // return { - // folder: output, - // binary: undefined - // } - } - } catch (e) { + setOutput("output", cargoOutputPath); + setOutput("binary", join(cargoOutputPath, binName)); + return { + folder: cargoOutputPath, + binary: join(cargoOutputPath, binName), + }; + } else if (action === "make") { + // TODO: + throw new Error("Unsupported action"); + } else if (action === "preview") { + // continue + } else { + throw new Error("Unsupported action"); + // const output = join(destinationFolder, 'out', 'make') + // setOutput('output', output) + // return { + // folder: output, + // binary: undefined + // } + } + } catch (e) { if (e instanceof Error) { if (e.name === "RequestError") { log("Request error"); diff --git a/plugins/plugin-tauri/src/utils.ts b/plugins/plugin-tauri/src/utils.ts index 9520f0c2..2667758c 100644 --- a/plugins/plugin-tauri/src/utils.ts +++ b/plugins/plugin-tauri/src/utils.ts @@ -26,4 +26,5 @@ export const defaultTauriConfig = { openDevtoolsOnStart: false, enableDoctor: false, serverMode: "default" as const, + mobile: {}, } satisfies DesktopApp.Tauri; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 75176761..3039e287 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -235,6 +235,31 @@ importers: specifier: ^3.2.8 version: 3.3.1(typescript@6.0.3) + apps/mobile: + dependencies: + '@pipelab/constants': + specifier: workspace:* + version: link:../../packages/constants + '@pipelab/shared': + specifier: workspace:* + version: link:../../packages/shared + devDependencies: + '@tauri-apps/cli': + specifier: 2.4.1 + version: 2.4.1 + '@types/node': + specifier: 24.12.2 + version: 24.12.2 + rimraf: + specifier: 6.0.1 + version: 6.0.1 + typescript: + specifier: 5.9.3 + version: 5.9.3 + vite: + specifier: 6.3.3 + version: 6.3.3(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.19.1) + apps/ui: dependencies: '@codemirror/autocomplete': @@ -2138,105 +2163,89 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -2377,6 +2386,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/cliui@9.0.0': + resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} + engines: {node: '>=18'} + '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -2611,28 +2624,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@next/swc-linux-arm64-musl@16.2.3': resolution: {integrity: sha512-/YV0LgjHUmfhQpn9bVoGc4x4nan64pkhWR5wyEV8yCOfwwrH630KpvRg86olQHTwHIn1z59uh6JwKvHq1h4QEw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@next/swc-linux-x64-gnu@16.2.3': resolution: {integrity: sha512-/HiWEcp+WMZ7VajuiMEFGZ6cg0+aYZPqCJD3YJEfpVWQsKYSjXQG06vJP6F1rdA03COD9Fef4aODs3YxKx+RDQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@next/swc-linux-x64-musl@16.2.3': resolution: {integrity: sha512-Kt44hGJfZSefebhk/7nIdivoDr3Ugp5+oNz9VvF3GUtfxutucUIHfIO0ZYO8QlOPDQloUVQn4NVC/9JvHRk9hw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@next/swc-win32-arm64-msvc@16.2.3': resolution: {integrity: sha512-O2NZ9ie3Tq6xj5Z5CSwBT3+aWAMW2PIZ4egUi9MaWLkwaehgtB7YZjPm+UpcNpKOme0IQuqDcor7BsW6QBiQBw==} @@ -2841,56 +2850,48 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-arm64-musl@0.40.0': resolution: {integrity: sha512-tGmWhLD/0YMotCdfezlT6tC/MJG/wKpo4vnQ3Cq+4eBk/BwNv7EmkD0VkD5F/dYkT3b8FNU01X2e8vvJuWoM1w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@oxfmt/binding-linux-ppc64-gnu@0.40.0': resolution: {integrity: sha512-rVbFyM3e7YhkVnp0IVYjaSHfrBWcTRWb60LEcdNAJcE2mbhTpbqKufx0FrhWfoxOrW/+7UJonAOShoFFLigDqQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-riscv64-gnu@0.40.0': resolution: {integrity: sha512-3ZqBw14JtWeEoLiioJcXSJz8RQyPE+3jLARnYM1HdPzZG4vk+Ua8CUupt2+d+vSAvMyaQBTN2dZK+kbBS/j5mA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-riscv64-musl@0.40.0': resolution: {integrity: sha512-JJ4PPSdcbGBjPvb+O7xYm2FmAsKCyuEMYhqatBAHMp/6TA6rVlf9Z/sYPa4/3Bommb+8nndm15SPFRHEPU5qFA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [musl] '@oxfmt/binding-linux-s390x-gnu@0.40.0': resolution: {integrity: sha512-Kp0zNJoX9Ik77wUya2tpBY3W9f40VUoMQLWVaob5SgCrblH/t2xr/9B2bWHfs0WCefuGmqXcB+t0Lq77sbBmZw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-x64-gnu@0.40.0': resolution: {integrity: sha512-7YTCNzleWTaQTqNGUNQ66qVjpoV6DjbCOea+RnpMBly2bpzrI/uu7Rr+2zcgRfNxyjXaFTVQKaRKjqVdeUfeVA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-x64-musl@0.40.0': resolution: {integrity: sha512-hWnSzJ0oegeOwfOEeejYXfBqmnRGHusgtHfCPzmvJvHTwy1s3Neo59UKc1CmpE3zxvrCzJoVHos0rr97GHMNPw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@oxfmt/binding-openharmony-arm64@0.40.0': resolution: {integrity: sha512-28sJC1lR4qtBJGzSRRbPnSW3GxU2+4YyQFE6rCmsUYqZ5XYH8jg0/w+CvEzQ8TuAQz5zLkcA25nFQGwoU0PT3Q==} @@ -2963,56 +2964,48 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-arm64-musl@1.55.0': resolution: {integrity: sha512-/kp65avi6zZfqEng56TTuhiy3P/3pgklKIdf38yvYeJ9/PgEeRA2A2AqKAKbZBNAqUzrzHhz9jF6j/PZvhJzTQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@oxlint/binding-linux-ppc64-gnu@1.55.0': resolution: {integrity: sha512-A6pTdXwcEEwL/nmz0eUJ6WxmxcoIS+97GbH96gikAyre3s5deC7sts38ZVVowjS2QQFuSWkpA4ZmQC0jZSNvJQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-riscv64-gnu@1.55.0': resolution: {integrity: sha512-clj0lnIN+V52G9tdtZl0LbdTSurnZ1NZj92Je5X4lC7gP5jiCSW+Y/oiDiSauBAD4wrHt2S7nN3pA0zfKYK/6Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-riscv64-musl@1.55.0': resolution: {integrity: sha512-NNu08pllN5x/O94/sgR3DA8lbrGBnTHsINZZR0hcav1sj79ksTiKKm1mRzvZvacwQ0hUnGinFo+JO75ok2PxYg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [musl] '@oxlint/binding-linux-s390x-gnu@1.55.0': resolution: {integrity: sha512-BvfQz3PRlWZRoEZ17dZCqgQsMRdpzGZomJkVATwCIGhHVVeHJMQdmdXPSjcT1DCNUrOjXnVyj1RGDj5+/Je2+Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@oxlint/binding-linux-x64-gnu@1.55.0': resolution: {integrity: sha512-ngSOoFCSBMKVQd24H8zkbcBNc7EHhjnF1sv3mC9NNXQ/4rRjI/4Dj9+9XoDZeFEkF1SX1COSBXF1b2Pr9rqdEw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-x64-musl@1.55.0': resolution: {integrity: sha512-BDpP7W8GlaG7BR6QjGZAleYzxoyKc/D24spZIF2mB3XsfALQJJT/OBmP8YpeTb1rveFSBHzl8T7l0aqwkWNdGA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@oxlint/binding-openharmony-arm64@1.55.0': resolution: {integrity: sha512-PS6GFvmde/pc3fCA2Srt51glr8Lcxhpf6WIBFfLphndjRrD34NEcses4TSxQrEcxYo6qVywGfylM0ZhSCF2gGA==} @@ -3067,42 +3060,36 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.6': resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.6': resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.6': resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.6': resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.6': resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-win32-arm64@2.5.6': resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} @@ -3336,126 +3323,108 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': resolution: {integrity: sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-gnu@1.0.2': resolution: {integrity: sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': resolution: {integrity: sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': resolution: {integrity: sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-arm64-musl@1.0.2': resolution: {integrity: sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': resolution: {integrity: sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9': resolution: {integrity: sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-ppc64-gnu@1.0.2': resolution: {integrity: sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': resolution: {integrity: sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': resolution: {integrity: sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.2': resolution: {integrity: sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': resolution: {integrity: sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': resolution: {integrity: sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.2': resolution: {integrity: sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': resolution: {integrity: sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': resolution: {integrity: sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-linux-x64-musl@1.0.2': resolution: {integrity: sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': resolution: {integrity: sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==} @@ -3584,79 +3553,66 @@ packages: resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.60.1': resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.60.1': resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.60.1': resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.60.1': resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.60.1': resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} cpu: [loong64] os: [linux] - libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.60.1': resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.60.1': resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} cpu: [ppc64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.60.1': resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.60.1': resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.60.1': resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.1': resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.60.1': resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openbsd-x64@4.60.1': resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} @@ -3757,6 +3713,77 @@ packages: resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} + '@tauri-apps/cli-darwin-arm64@2.4.1': + resolution: {integrity: sha512-QME7s8XQwy3LWClTVlIlwXVSLKkeJ/z88pr917Mtn9spYOjnBfsgHAgGdmpWD3NfJxjg7CtLbhH49DxoFL+hLg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tauri-apps/cli-darwin-x64@2.4.1': + resolution: {integrity: sha512-/r89IcW6Ya1sEsFUEH7wLNruDTj7WmDWKGpPy7gATFtQr5JEY4heernqE82isjTUimnHZD8SCr0jA3NceI4ybw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tauri-apps/cli-linux-arm-gnueabihf@2.4.1': + resolution: {integrity: sha512-9tDijkRB+CchAGjXxYdY9l/XzFpLp1yihUtGXJz9eh+3qIoRI043n3e+6xmU8ZURr7XPnu+R4sCmXs6HD+NCEQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tauri-apps/cli-linux-arm64-gnu@2.4.1': + resolution: {integrity: sha512-pnFGDEXBAzS4iDYAVxTRhAzNu3K2XPGflYyBc0czfHDBXopqRgMyj5Q9Wj7HAwv6cM8BqzXINxnb2ZJFGmbSgA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tauri-apps/cli-linux-arm64-musl@2.4.1': + resolution: {integrity: sha512-Hp0zXgeZNKmT+eoJSCxSBUm2QndNuRxR55tmIeNm3vbyUMJN/49uW7nurZ5fBPsacN4Pzwlx1dIMK+Gnr9A69w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tauri-apps/cli-linux-riscv64-gnu@2.4.1': + resolution: {integrity: sha512-3T3bo2E4fdYRvzcXheWUeQOVB+LunEEi92iPRgOyuSVexVE4cmHYl+MPJF+EUV28Et0hIVTsHibmDO0/04lAFg==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + + '@tauri-apps/cli-linux-x64-gnu@2.4.1': + resolution: {integrity: sha512-kLN0FdNONO+2i+OpU9+mm6oTGufRC00e197TtwjpC0N6K2K8130w7Q3FeODIM2CMyg0ov3tH+QWqKW7GNhHFzg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tauri-apps/cli-linux-x64-musl@2.4.1': + resolution: {integrity: sha512-a8exvA5Ub9eg66a6hsMQKJIkf63QAf9OdiuFKOsEnKZkNN2x0NLgfvEcqdw88VY0UMs9dBoZ1AGbWMeYnLrLwQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tauri-apps/cli-win32-arm64-msvc@2.4.1': + resolution: {integrity: sha512-4JFrslsMCJQG1c573T9uqQSAbF3j/tMKkMWzsIssv8jvPiP++OG61A2/F+y9te9/Q/O95cKhDK63kaiO5xQaeg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tauri-apps/cli-win32-ia32-msvc@2.4.1': + resolution: {integrity: sha512-9eXfFORehYSCRwxg2KodfmX/mhr50CI7wyBYGbPLePCjr5z0jK/9IyW6r0tC+ZVjwpX48dkk7hKiUgI25jHjzA==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@tauri-apps/cli-win32-x64-msvc@2.4.1': + resolution: {integrity: sha512-60a4Ov7Jrwqz2hzDltlS7301dhSAmM9dxo+IRBD3xz7yobKrgaHXYpWvnRomYItHcDd51VaKc9292H8/eE/gsw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tauri-apps/cli@2.4.1': + resolution: {integrity: sha512-9Ta81jx9+57FhtU/mPIckDcOBtPTUdKM75t4+aA0X84b8Sclb0jy1xA8NplmcRzp2fsfIHNngU2NiRxsW5+yOQ==} + engines: {node: '>= 10'} + hasBin: true + '@tootallnate/once@2.0.0': resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} @@ -4241,6 +4268,7 @@ packages: '@xmldom/xmldom@0.8.12': resolution: {integrity: sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==} engines: {node: '>=10.0.0'} + deprecated: this version has critical issues, please update to the latest version '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -5383,6 +5411,12 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true + glob@11.1.0: + resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} + engines: {node: 20 || >=22} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + glob@13.0.6: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} @@ -5745,6 +5779,10 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jackspeak@4.2.3: + resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} + engines: {node: 20 || >=22} + jest-worker@27.5.1: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} @@ -5876,28 +5914,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -6969,6 +7003,11 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + rimraf@6.0.1: + resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==} + engines: {node: 20 || >=22} + hasBin: true + roarr@2.15.4: resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} engines: {node: '>=8.0'} @@ -7080,56 +7119,48 @@ packages: engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] - libc: glibc sass-embedded-linux-arm@1.99.0: resolution: {integrity: sha512-d4IjJZrX2+AwB2YCy1JySwdptJECNP/WfAQLUl8txI3ka8/d3TUI155GtelnoZUkio211PwIeFvvAeZ9RXPQnw==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] - libc: glibc sass-embedded-linux-musl-arm64@1.99.0: resolution: {integrity: sha512-Hi2bt/IrM5P4FBKz6EcHAlniwfpoz9mnTdvSd58y+avA3SANM76upIkAdSayA8ZGwyL3gZokru1AKDPF9lJDNw==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] - libc: musl sass-embedded-linux-musl-arm@1.99.0: resolution: {integrity: sha512-2gvHOupgIw3ytatXT4nFUow71LFbuOZPEwG+HUzcNQDH8ue4Ez8cr03vsv5MDv3lIjOKcXwDvWD980t18MwkoQ==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] - libc: musl sass-embedded-linux-musl-riscv64@1.99.0: resolution: {integrity: sha512-mKqGvVaJ9rHMqyZsF0kikQe4NO0f4osb67+X6nLhBiVDKvyazQHJ3zJQreNefIE36yL2sjHIclSB//MprzaQDg==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] - libc: musl sass-embedded-linux-musl-x64@1.99.0: resolution: {integrity: sha512-huhgOMmOc30r7CH7qbRbT9LerSEGSnWuS4CYNOskr9BvNeQp4dIneFufNRGZ7hkOAxUM8DglxIZJN/cyAT95Ew==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] - libc: musl sass-embedded-linux-riscv64@1.99.0: resolution: {integrity: sha512-mevFPIFAVhrH90THifxLfOntFmHtcEKOcdWnep2gJ0X4DVva4AiVIRlQe/7w9JFx5+gnDRE1oaJJkzuFUuYZsA==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] - libc: glibc sass-embedded-linux-x64@1.99.0: resolution: {integrity: sha512-9k7IkULqIZdCIVt4Mboryt6vN8Mjmm3EhI1P3mClU5y5i3wLK5ExC3cbVWk047KsID/fvB1RLslqghXJx5BoxA==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] - libc: glibc sass-embedded-unknown-all@1.99.0: resolution: {integrity: sha512-P7MxiUtL/XzGo3PX0CaB8lNNEFLQWKikPA8pbKytx9ZCLZSDkt2NJcdAbblB/sqMs4AV3EK2NadV8rI/diq3xg==} @@ -7616,6 +7647,7 @@ packages: tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} + deprecated: unmaintained hasBin: true peerDependencies: typescript: ^5.0.0 @@ -9944,6 +9976,8 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/cliui@9.0.0': {} + '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.3 @@ -11058,6 +11092,53 @@ snapshots: dependencies: defer-to-connect: 2.0.1 + '@tauri-apps/cli-darwin-arm64@2.4.1': + optional: true + + '@tauri-apps/cli-darwin-x64@2.4.1': + optional: true + + '@tauri-apps/cli-linux-arm-gnueabihf@2.4.1': + optional: true + + '@tauri-apps/cli-linux-arm64-gnu@2.4.1': + optional: true + + '@tauri-apps/cli-linux-arm64-musl@2.4.1': + optional: true + + '@tauri-apps/cli-linux-riscv64-gnu@2.4.1': + optional: true + + '@tauri-apps/cli-linux-x64-gnu@2.4.1': + optional: true + + '@tauri-apps/cli-linux-x64-musl@2.4.1': + optional: true + + '@tauri-apps/cli-win32-arm64-msvc@2.4.1': + optional: true + + '@tauri-apps/cli-win32-ia32-msvc@2.4.1': + optional: true + + '@tauri-apps/cli-win32-x64-msvc@2.4.1': + optional: true + + '@tauri-apps/cli@2.4.1': + optionalDependencies: + '@tauri-apps/cli-darwin-arm64': 2.4.1 + '@tauri-apps/cli-darwin-x64': 2.4.1 + '@tauri-apps/cli-linux-arm-gnueabihf': 2.4.1 + '@tauri-apps/cli-linux-arm64-gnu': 2.4.1 + '@tauri-apps/cli-linux-arm64-musl': 2.4.1 + '@tauri-apps/cli-linux-riscv64-gnu': 2.4.1 + '@tauri-apps/cli-linux-x64-gnu': 2.4.1 + '@tauri-apps/cli-linux-x64-musl': 2.4.1 + '@tauri-apps/cli-win32-arm64-msvc': 2.4.1 + '@tauri-apps/cli-win32-ia32-msvc': 2.4.1 + '@tauri-apps/cli-win32-x64-msvc': 2.4.1 + '@tootallnate/once@2.0.0': {} '@trpc/client@10.45.2(@trpc/server@10.45.2)': @@ -11664,7 +11745,7 @@ snapshots: '@vue/shared': 3.5.13 estree-walker: 2.0.2 magic-string: 0.30.21 - postcss: 8.5.9 + postcss: 8.5.15 source-map-js: 1.2.1 '@vue/compiler-sfc@3.5.32': @@ -11676,7 +11757,7 @@ snapshots: '@vue/shared': 3.5.32 estree-walker: 2.0.2 magic-string: 0.30.21 - postcss: 8.5.9 + postcss: 8.5.15 source-map-js: 1.2.1 '@vue/compiler-sfc@3.5.34': @@ -13192,6 +13273,15 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@11.1.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.2.3 + minimatch: 10.2.5 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.2 + glob@13.0.6: dependencies: minimatch: 10.2.5 @@ -13522,6 +13612,10 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jackspeak@4.2.3: + dependencies: + '@isaacs/cliui': 9.0.0 + jest-worker@27.5.1: dependencies: '@types/node': 24.12.4 @@ -14803,6 +14897,11 @@ snapshots: dependencies: glob: 7.2.3 + rimraf@6.0.1: + dependencies: + glob: 11.1.0 + package-json-from-dist: 1.0.1 + roarr@2.15.4: dependencies: boolean: 3.2.0 @@ -15898,7 +15997,7 @@ snapshots: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - postcss: 8.5.9 + postcss: 8.5.15 rollup: 4.60.1 tinyglobby: 0.2.16 optionalDependencies: From 6882716fffdc360e000ddf00f1a85e27ccfa6130 Mon Sep 17 00:00:00 2001 From: Quentin Goinaud Date: Fri, 14 Aug 2026 06:59:22 +0000 Subject: [PATCH 2/5] fix(plugin-tauri): store Android SDK in a pipeline-independent shared cache Previously the SDK/NDK (several GB) was written under the per-pipeline cache (CacheFolder.Pipelines/), so every Android pipeline re-downloaded its own copy. Point ensureAndroidEnvironment at getCachePath('Pipelines')/android-sdk so the toolchain is shared across all pipelines. --- plugins/plugin-tauri/src/tauri.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/plugin-tauri/src/tauri.ts b/plugins/plugin-tauri/src/tauri.ts index ee757837..7ea16d00 100644 --- a/plugins/plugin-tauri/src/tauri.ts +++ b/plugins/plugin-tauri/src/tauri.ts @@ -1025,7 +1025,16 @@ export const tauri = async ( // Self-heal the Android toolchain (SDK/NDK + Rust target) when building // for Android on a host that doesn't have them installed yet. if (isAndroidBuild) { - const androidHome = await ensureAndroidEnvironment(cache, cargoBinDir, node, log, abortSignal); + // Store the SDK in a pipeline-independent location so it is shared by + // every Android pipeline instead of being re-downloaded (several GB) + // per pipeline. + const androidHome = await ensureAndroidEnvironment( + context.getCachePath("Pipelines"), + cargoBinDir, + node, + log, + abortSignal, + ); // Make sure the SDK tooling is on PATH for the Tauri CLI / Gradle. process.env.PATH = `${join(androidHome, "cmdline-tools", "latest", "bin")}${delimiter}${join( androidHome, From c127a014ae55d6cea8ccd43e5afc809c1d4d6a9c Mon Sep 17 00:00:00 2001 From: Quentin Goinaud Date: Fri, 14 Aug 2026 07:05:24 +0000 Subject: [PATCH 3/5] fix(plugin-tauri): use top-level cache base for shared Android SDK Use context.getCachePath() (the cache base, alongside pacote) instead of the per-pipeline cache so the SDK lives at cache/android-sdk. Also fixes a typecheck bug: the CacheFolder value is lowercase 'pipelines', so the previous getCachePath('Pipelines') literal would fail the typecheck. --- plugins/plugin-tauri/src/tauri.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/plugin-tauri/src/tauri.ts b/plugins/plugin-tauri/src/tauri.ts index 7ea16d00..d3f063c6 100644 --- a/plugins/plugin-tauri/src/tauri.ts +++ b/plugins/plugin-tauri/src/tauri.ts @@ -1025,11 +1025,13 @@ export const tauri = async ( // Self-heal the Android toolchain (SDK/NDK + Rust target) when building // for Android on a host that doesn't have them installed yet. if (isAndroidBuild) { - // Store the SDK in a pipeline-independent location so it is shared by - // every Android pipeline instead of being re-downloaded (several GB) - // per pipeline. + // Store the SDK in a pipeline-independent location (top-level cache, + // alongside `pacote`) so it is shared by every Android pipeline instead + // of being re-downloaded (several GB) per pipeline. It is also + // regenerable, so it belongs in the cache folder (which is relocatable + // via settings.cacheFolder). const androidHome = await ensureAndroidEnvironment( - context.getCachePath("Pipelines"), + context.getCachePath(), cargoBinDir, node, log, From 537d4ced632010cf8018a9a63da153475715b8dd Mon Sep 17 00:00:00 2001 From: Quentin Goinaud Date: Fri, 14 Aug 2026 07:15:16 +0000 Subject: [PATCH 4/5] fix(plugin-tauri): drop no-op mobileConfig param and dead configuration field - Remove the mobileConfig input: it was merged into completeConfiguration.mobile but then deleted before the config was written (to stay valid across Tauri 2.x), so it silently did nothing and was misleading. - Remove the unused `configuration` field from the shared params const (the package action uses configureParams, not it). - Update the example pipeline accordingly. --- examples/tauri-mobile-pipeline/pipeline.json | 3 +-- plugins/plugin-tauri/src/package.ts | 11 ----------- plugins/plugin-tauri/src/tauri.ts | 20 -------------------- 3 files changed, 1 insertion(+), 33 deletions(-) diff --git a/examples/tauri-mobile-pipeline/pipeline.json b/examples/tauri-mobile-pipeline/pipeline.json index 0d3c314c..228b3592 100644 --- a/examples/tauri-mobile-pipeline/pipeline.json +++ b/examples/tauri-mobile-pipeline/pipeline.json @@ -42,8 +42,7 @@ "enableSteamSupport": { "editor": "simple", "value": false }, "steamGameId": { "editor": "simple", "value": 480 }, "enableDiscordSupport": { "editor": "simple", "value": false }, - "discordAppId": { "editor": "simple", "value": "\"\"" }, - "mobileConfig": { "editor": "simple", "value": "{}" } + "discordAppId": { "editor": "simple", "value": "\"\"" } } } ], diff --git a/plugins/plugin-tauri/src/package.ts b/plugins/plugin-tauri/src/package.ts index 5da1ac14..8ad502a6 100644 --- a/plugins/plugin-tauri/src/package.ts +++ b/plugins/plugin-tauri/src/package.ts @@ -3,16 +3,6 @@ import { createPackageV2Props, tauri } from "./tauri"; import { merge } from "ts-deepmerge"; import { defaultTauriConfig } from "./utils"; -function parseMobileConfig(value: unknown): DesktopApp.Tauri["mobile"] { - if (!value) return {}; - if (typeof value === "object") return value as DesktopApp.Tauri["mobile"]; - try { - return JSON.parse(value as string) as DesktopApp.Tauri["mobile"]; - } catch { - return {}; - } -} - export const packageV2Runner = createActionRunner>( async (options) => { const appFolder = options.inputs["input-folder"]; @@ -44,7 +34,6 @@ export const packageV2Runner = createActionRunner | undefined, - required: true, - control: { - type: "json", - }, - }, } satisfies InputsDefinition; export const configureParams = { @@ -468,18 +460,6 @@ export const configureParams = { description: "The Discord application ID", }), - // Mobile specific configuration (android/ios). Merged into the generated - // `app.mobile` section of tauri.conf.json when packaging for a mobile target. - mobileConfig: { - label: "Mobile configuration", - required: false, - description: - "Optional Tauri mobile configuration (android/ios overrides). Only used when packaging for a mobile platform.", - value: "{}", - control: { - type: "json", - }, - }, } satisfies InputsDefinition; const outputs = { From ac6130cb69569d44c4e177c84b7b94fbbfa9526e Mon Sep 17 00:00:00 2001 From: Quentin Goinaud Date: Fri, 14 Aug 2026 07:34:40 +0000 Subject: [PATCH 5/5] fix(plugin-tauri): accurate platform/arch types + reusable mobile build path - Add TargetPlatform/TargetArch types (NodeJS.Platform/Architecture don't know about android/ios/armv7l/universal/mips64el) and use them for the platform/ arch param values, the handler's finalPlatform/finalArch, and the mobile target-resolution helpers. - Export ensureAndroidEnvironment/findAndroidApk/resolveMobileCliTarget/ resolveMobileOutputTriple (and re-export from the plugin entry) so future mobile-first plugins can reuse the SDK provisioning and target resolution. - Example now builds the SAME app for both desktop (linux) and android from one input folder. --- examples/tauri-mobile-pipeline/README.md | 33 +++++++++------ examples/tauri-mobile-pipeline/pipeline.json | 44 +++++++++++++++++++- plugins/plugin-tauri/src/index.ts | 7 ++++ plugins/plugin-tauri/src/tauri.ts | 32 ++++++++------ 4 files changed, 90 insertions(+), 26 deletions(-) diff --git a/examples/tauri-mobile-pipeline/README.md b/examples/tauri-mobile-pipeline/README.md index da277670..0d643dcd 100644 --- a/examples/tauri-mobile-pipeline/README.md +++ b/examples/tauri-mobile-pipeline/README.md @@ -1,36 +1,41 @@ -# Tauri Mobile Pipeline +# Tauri Mobile + Desktop Pipeline -Builds a Tauri app for **Android** and **iOS** using the Pipelab Tauri plugin's -mobile build path. +Builds the **same** sample web app into both a **desktop** bundle and an +**Android** build using the Pipelab Tauri plugin. One Tauri project, two +targets — the example canvas contains a desktop (`platform: linux`) package +block and a mobile (`platform: android`) package block that both consume the +same `html-export` input folder. ## What it does 1. `tauri:scaffold` — creates a fresh Tauri project (web frontend + `src-tauri`). 2. `tauri:package` — writes a mobile-ready `tauri.conf.json` (enables all bundle targets) and runs the platform build: + - Desktop: `cargo tauri build` (target selected via `platform`/`arch`). - Android: `cargo tauri android init` then `cargo tauri android build --target aarch64 --apk` - iOS: `cargo tauri ios init` then `cargo tauri ios build --target aarch64` -The `platform` and `arch` action inputs select the mobile target. Use the -`mobileConfig` input to inject `tauri.conf.json > app > mobile` (e.g. -`"homeIndicator": "hidden"`, `"orientation": "portrait"`). +The `platform` and `arch` action inputs select the target. `platform` accepts +`win32` / `linux` / `darwin` (desktop) as well as `android` / `ios` (mobile), +and `arch` accepts `x64` / `arm64` / `ia32` / `armv7l` / `universal` / ... ## Prerequisites -Mobile builds are delegated to the official Tauri CLI, which requires the native +Builds are delegated to the official Tauri CLI, which requires the native toolchains: +- **Desktop** — the host's system webview libraries (e.g. `webkit2gtk-4.1`, + `gtk+-3.0`, `librsvg-2.0` on Linux). See + https://tauri.app/start/prerequisites/#linux - **Android** — Android SDK + NDK, `ANDROID_HOME` set, and the SDK packages `platform-tools`, `build-tools`, plus a platform/NDK matching your `rustup` - targets (`aarch64-linux-android`, ...). See + targets (`aarch64-linux-android`, ...). The Pipelab plugin **self-heals** this + on first run (it reuses an existing SDK or downloads one into the shared cache + folder and adds the Rust target). See https://tauri.app/start/prerequisites/#android - **iOS** — macOS with Xcode and the iOS SDK. See https://tauri.app/start/prerequisites/#ios -Without these, the pipeline will report `Android SDK not found` / -`failed to ensure iOS environment` from the Tauri CLI — this is expected in -environments that only have the Rust + desktop toolchain. - ## Run ```bash @@ -38,3 +43,7 @@ pnpm --filter @pipelab/cli start -- \ run examples/tauri-mobile-pipeline/pipeline.json \ --user-data /tmp/pl-userdata ``` + +On a host that only has the Rust + desktop toolchain, the Android block will +still succeed (the plugin provisions the SDK into the shared cache); the desktop +block additionally needs the webview system libraries installed. diff --git a/examples/tauri-mobile-pipeline/pipeline.json b/examples/tauri-mobile-pipeline/pipeline.json index 228b3592..1f3f142b 100644 --- a/examples/tauri-mobile-pipeline/pipeline.json +++ b/examples/tauri-mobile-pipeline/pipeline.json @@ -1,10 +1,50 @@ { "version": "3.0.0", - "name": "Mobile Tauri Package", - "description": "Packages the sample web app into an Android (or iOS) build using @pipelab/plugin-tauri with mobile targets enabled.", + "name": "Mobile + Desktop Tauri Package", + "description": "Packages the SAME sample web app into both a desktop bundle (Linux) and an Android build using @pipelab/plugin-tauri, demonstrating one Tauri project targeting mobile and desktop.", "variables": [], "canvas": { "blocks": [ + { + "uid": "tauri-package-desktop", + "type": "action", + "origin": { "nodeId": "tauri:package:v2", "pluginId": "tauri" }, + "params": { + "arch": { "editor": "simple", "value": "\"x64\"" }, + "platform": { "editor": "simple", "value": "\"linux\"" }, + "input-folder": { + "editor": "simple", + "value": "\"/workspaces/pipelab/examples/tauri-mobile-pipeline/html-export\"" + }, + "name": { "editor": "simple", "value": "\"Pipelab Mobile\"" }, + "appBundleId": { "editor": "simple", "value": "\"app.pipelab.mobile\"" }, + "appCopyright": { "editor": "simple", "value": "\"Copyright © 2024 Pipelab\"" }, + "appVersion": { "editor": "simple", "value": "\"1.0.0\"" }, + "icon": { "editor": "simple", "value": "\"\"" }, + "author": { "editor": "simple", "value": "\"Pipelab\"" }, + "description": { "editor": "simple", "value": "\"A Pipelab mobile demo app\"" }, + "appCategoryType": { + "editor": "simple", + "value": "\"public.app-category.developer-tools\"" + }, + "width": { "editor": "simple", "value": 400 }, + "height": { "editor": "simple", "value": 800 }, + "fullscreen": { "editor": "simple", "value": false }, + "frame": { "editor": "simple", "value": true }, + "transparent": { "editor": "simple", "value": false }, + "toolbar": { "editor": "simple", "value": true }, + "alwaysOnTop": { "editor": "simple", "value": false }, + "tauriVersion": { "editor": "simple", "value": "\"\"" }, + "enableExtraLogging": { "editor": "simple", "value": false }, + "openDevtoolsOnStart": { "editor": "simple", "value": false }, + "websocketApi": { "editor": "simple", "value": "[]" }, + "ignore": { "editor": "editor", "value": "[\n]" }, + "enableSteamSupport": { "editor": "simple", "value": false }, + "steamGameId": { "editor": "simple", "value": 480 }, + "enableDiscordSupport": { "editor": "simple", "value": false }, + "discordAppId": { "editor": "simple", "value": "\"\"" } + } + }, { "uid": "tauri-package-mobile", "type": "action", diff --git a/plugins/plugin-tauri/src/index.ts b/plugins/plugin-tauri/src/index.ts index d0981587..cf278efa 100644 --- a/plugins/plugin-tauri/src/index.ts +++ b/plugins/plugin-tauri/src/index.ts @@ -13,6 +13,13 @@ import { import { configureRunner, props } from "./configure"; import { packageV2Runner } from "./package"; +export { + ensureAndroidEnvironment, + findAndroidApk, + resolveMobileCliTarget, + resolveMobileOutputTriple, +} from "./tauri"; + export default createNodeDefinition({ nodes: [ // make and package diff --git a/plugins/plugin-tauri/src/tauri.ts b/plugins/plugin-tauri/src/tauri.ts index 47b5124b..9d9be2cb 100644 --- a/plugins/plugin-tauri/src/tauri.ts +++ b/plugins/plugin-tauri/src/tauri.ts @@ -23,6 +23,14 @@ import { execa } from "execa"; import { kebabCase } from "change-case"; import { parseTOML, stringifyTOML } from "confbox"; +// Platforms this plugin can target. `NodeJS.Platform` only knows the desktop +// operating systems, so we extend it with the mobile targets Tauri supports. +export type TargetPlatform = NodeJS.Platform | "android" | "ios"; + +// Architectures this plugin can target. `NodeJS.Architecture` is missing a few +// values surfaced by the architecture selector (armv7l, universal, mips64el). +export type TargetArch = NodeJS.Architecture | "armv7l" | "universal" | "mips64el"; + /** * Searches for common cargo paths and resolves to a valid cargo executable path * @returns The path to the cargo executable @@ -100,9 +108,9 @@ async function resolveCargoPath(): Promise { * flag (an Android ABI or an iOS device/simulator slice), or undefined to let * Tauri pick its default. */ -function resolveMobileCliTarget( - platform: NodeJS.Platform, - arch: NodeJS.Architecture, +export function resolveMobileCliTarget( + platform: TargetPlatform, + arch: TargetArch, ): string | undefined { if (platform === "android") { switch (arch) { @@ -139,9 +147,9 @@ function resolveMobileCliTarget( * Resolves the Rust target triple actually produced by the build, used to * locate the generated artifact on disk. */ -function resolveMobileOutputTriple( - platform: NodeJS.Platform, - arch: NodeJS.Architecture, +export function resolveMobileOutputTriple( + platform: TargetPlatform, + arch: TargetArch, ): string | undefined { if (platform === "android") { switch (arch) { @@ -197,7 +205,7 @@ const paramsInputURL = { const params = { arch: { - value: "" as NodeJS.Architecture | "", // MakeOptions['arch'], + value: "" as TargetArch | "", // MakeOptions['arch'], label: "Architecture", required: false, control: { @@ -234,7 +242,7 @@ const params = { }, }, platform: { - value: "" as NodeJS.Platform | "", // MakeOptions['platform'], + value: "" as TargetPlatform | "", // MakeOptions['platform'], label: "Platform", required: false, control: { @@ -618,7 +626,7 @@ const ANDROID_CMDLINE_TOOLS_URL = * inside a `universal/release` subfolder) depending on the build target, so we * search for it instead of hard-coding a single path. */ -function findAndroidApk(root: string): string | undefined { +export function findAndroidApk(root: string): string | undefined { if (!existsSync(root)) return undefined; let releaseApk: string | undefined; const walk = (dir: string): void => { @@ -723,7 +731,7 @@ async function provisionAndroidSdk( * downloads the command-line tools and provisions the NDK/build-tools/platform * into a cache directory, then exposes it via `ANDROID_HOME`. */ -async function ensureAndroidEnvironment( +export async function ensureAndroidEnvironment( cacheDir: string, cargoBinDir: string, node: string, @@ -942,9 +950,9 @@ export const tauri = async ( try { log("typeof inputs.platform", typeof inputs.platform); - const finalPlatform: NodeJS.Platform = inputPlatform ?? osPlatform(); + const finalPlatform: TargetPlatform = inputPlatform ?? osPlatform(); log("finalPlatform", finalPlatform); - const finalArch: NodeJS.Architecture = inputArch ?? (osArch() as NodeJS.Architecture); + const finalArch: TargetArch = inputArch ?? osArch(); log("finalArch", finalArch); const isAndroidBuild = finalPlatform === "android";