diff --git a/.changes/cli-migrate-enhancemnets.md b/.changes/cli-migrate-enhancemnets.md new file mode 100644 index 000000000000..e95abb6158ac --- /dev/null +++ b/.changes/cli-migrate-enhancemnets.md @@ -0,0 +1,14 @@ +--- +"tauri-cli": "patch:enhance" +"@tauri-apps/cli": "patch:enhance" +--- + +Enhance `tauri migrate` to also migrate variables like `appWindow`: +```ts +import { appWindow } from '@tauri-apps/api/window' +``` +will become: +```ts +import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow' +const appWindow = getCurrentWebviewWindow() +``` diff --git a/.changes/cli-migrate-fix-renamed-modules.md b/.changes/cli-migrate-fix-renamed-modules.md new file mode 100644 index 000000000000..dc6909d67083 --- /dev/null +++ b/.changes/cli-migrate-fix-renamed-modules.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": "patch:bug" +"@tauri-apps/cli": "patch:bug" +--- + +Fix `tauri migrate` incorrectly migrating `@tauri-apps/api/tauri` module to just `core` and `@tauri-apps/api/window` to just `webviewWindow`. diff --git a/examples/api/src-tauri/Cargo.toml b/examples/api/src-tauri/Cargo.toml index fd489ff2dd84..63d1768d2908 100644 --- a/examples/api/src-tauri/Cargo.toml +++ b/examples/api/src-tauri/Cargo.toml @@ -8,7 +8,7 @@ license = "Apache-2.0 OR MIT" [lib] name = "api_lib" -crate-type = ["staticlib", "cdylib", "rlib"] +crate-type = ["staticlib", "cdylib", "lib"] [build-dependencies] tauri-build = { path = "../../../core/tauri-build", features = [ diff --git a/tooling/cli/Cargo.lock b/tooling/cli/Cargo.lock index 5d8f86d56ac1..d19d34448327 100644 --- a/tooling/cli/Cargo.lock +++ b/tooling/cli/Cargo.lock @@ -75,6 +75,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -162,6 +168,12 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "assert-unchecked" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7330592adf847ee2e3513587b4db2db410a0d751378654e7e993d9adcbe5c795" + [[package]] name = "async-lock" version = "3.3.0" @@ -347,9 +359,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitness" @@ -422,9 +434,12 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.3" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +dependencies = [ + "allocator-api2", +] [[package]] name = "bytecount" @@ -526,6 +541,15 @@ dependencies = [ "cipher", ] +[[package]] +name = "castaway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.0.88" @@ -663,6 +687,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa 1.0.10", + "ryu", + "static_assertions", +] + [[package]] name = "concurrent-queue" version = "2.4.0" @@ -975,6 +1012,20 @@ dependencies = [ "syn 2.0.52", ] +[[package]] +name = "dashmap" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.5.0" @@ -2338,7 +2389,7 @@ dependencies = [ "parking_lot", "pin-project", "rand 0.8.5", - "rustc-hash", + "rustc-hash 1.1.0", "serde", "serde_json", "thiserror", @@ -2536,7 +2587,7 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "libc", "redox_syscall", ] @@ -2610,6 +2661,19 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "magic_string" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8033ce8c43f7ccb207e4699f30eed50d7526379ee08fab47159f80b7934e18" +dependencies = [ + "base64 0.13.1", + "regex", + "serde", + "serde_json", + "vlq", +] + [[package]] name = "markup5ever" version = "0.11.0" @@ -2654,9 +2718,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" @@ -2667,6 +2731,31 @@ dependencies = [ "libc", ] +[[package]] +name = "miette" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" +dependencies = [ + "cfg-if", + "miette-derive", + "owo-colors", + "textwrap", + "thiserror", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + [[package]] name = "mime" version = "0.3.17" @@ -2718,7 +2807,7 @@ version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54a63d0570e4c3e0daf7a8d380563610e159f538e20448d6c911337246f40e84" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "ctor", "napi-derive", "napi-sys", @@ -2824,7 +2913,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "cfg-if", "libc", ] @@ -2851,7 +2940,7 @@ version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "crossbeam-channel", "filetime", "fsevent-sys", @@ -2891,11 +2980,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -2984,9 +3072,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -3060,7 +3148,7 @@ version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -3144,6 +3232,105 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "owo-colors" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" + +[[package]] +name = "oxc_allocator" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944ce079709849f7621902c7747141336fbdfa5419ae8da8ac11edc5679e977f" +dependencies = [ + "allocator-api2", + "bumpalo", +] + +[[package]] +name = "oxc_ast" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0814bc95355ba0ff7f60db5a01504cfdbbd44ae269cf4238356080964ae2ddbf" +dependencies = [ + "bitflags 2.6.0", + "num-bigint", + "oxc_allocator", + "oxc_ast_macros", + "oxc_span", + "oxc_syntax", +] + +[[package]] +name = "oxc_ast_macros" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34f6e9a0084937918818610e310edd9234b324863fcf46d9b5106b92fba30824" + +[[package]] +name = "oxc_diagnostics" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db41d3247dde8f6112233692d1d8065f8828cddf8c8735281b4a67238937d70b" +dependencies = [ + "miette", + "owo-colors", + "textwrap", + "unicode-width", +] + +[[package]] +name = "oxc_index" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bdb0c0067294f8919bf259393c5d8f36b8cff40ff63d698cc921cacec77fc58" + +[[package]] +name = "oxc_parser" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05c4d50a236bc5412b1702af99a4ddab88b60e8ef8d14178d74fba234fdd648" +dependencies = [ + "assert-unchecked", + "bitflags 2.6.0", + "memchr", + "num-bigint", + "num-traits", + "oxc_allocator", + "oxc_ast", + "oxc_diagnostics", + "oxc_span", + "oxc_syntax", + "rustc-hash 2.0.0", + "seq-macro", +] + +[[package]] +name = "oxc_span" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c8fb1f1dd18f8832b173939aff1c8a6fb2c50a6f95adf1aee817a65482e549" +dependencies = [ + "compact_str", + "miette", +] + +[[package]] +name = "oxc_syntax" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2909d1da9949782e08d633f46a4f3a52dcd3ef539b7a0ebfc5d02d7f6eb159" +dependencies = [ + "bitflags 2.6.0", + "dashmap", + "oxc_index", + "oxc_span", + "phf 0.11.2", + "rustc-hash 2.0.0", + "unicode-id-start", +] + [[package]] name = "p256" version = "0.13.2" @@ -3947,7 +4134,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e68a0d60350e5f4229cd69f08ec8f373e34424701702d1ee51a89aee1e9adcd1" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "bzip2", "chrono", "cpio", @@ -4013,6 +4200,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc_version" version = "0.4.0" @@ -4042,7 +4235,7 @@ version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys 0.4.13", @@ -4092,7 +4285,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7730060ad401b0d1807c904ea56735288af101430aa0d2ab8358b789f5f37002" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "bytemuck", "smallvec", "ttf-parser", @@ -4246,11 +4439,17 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +[[package]] +name = "seq-macro" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" + [[package]] name = "serde" -version = "1.0.197" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -4278,9 +4477,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", @@ -4309,9 +4508,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "indexmap 2.2.4", "itoa 1.0.10", @@ -4614,6 +4813,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "std_prelude" version = "0.2.12" @@ -4924,11 +5129,17 @@ dependencies = [ "libc", "local-ip-address", "log", + "magic_string", "minisign", "notify", "notify-debouncer-mini", "os_info", "os_pipe", + "oxc_allocator", + "oxc_ast", + "oxc_parser", + "oxc_span", + "phf 0.11.2", "plist", "regex", "resvg", @@ -5521,6 +5732,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" +[[package]] +name = "unicode-id-start" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f73150333cb58412db36f2aca8f2875b013049705cc77b94ded70a1ab1f5da" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -5568,9 +5785,9 @@ checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "universal-hash" @@ -5729,6 +5946,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vlq" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65dd7eed29412da847b0f78bcec0ac98588165988a8cfe41d4ea1d429f8ccfff" + [[package]] name = "vswhom" version = "0.1.0" diff --git a/tooling/cli/Cargo.toml b/tooling/cli/Cargo.toml index 030cfe8b17a6..b15ab92d47d8 100644 --- a/tooling/cli/Cargo.toml +++ b/tooling/cli/Cargo.toml @@ -92,6 +92,12 @@ css-color = "0.2" resvg = "0.42.0" dunce = "1" glob = "0.3" +oxc_parser = "0.16" +oxc_span = "0.16" +oxc_allocator = "0.16" +oxc_ast = "0.16" +magic_string = "0.3" +phf = { version = "0.11", features = ["macros"] } [target."cfg(windows)".dependencies.windows-sys] version = "0.52" diff --git a/tooling/cli/src/migrate/frontend.rs b/tooling/cli/src/migrate/frontend.rs index 55a3589afa83..3a9d0d97c96e 100644 --- a/tooling/cli/src/migrate/frontend.rs +++ b/tooling/cli/src/migrate/frontend.rs @@ -7,12 +7,20 @@ use crate::{ Result, }; use anyhow::Context; +use itertools::Itertools; +use magic_string::MagicString; +use oxc_allocator::Allocator; +use oxc_ast::ast::*; +use oxc_parser::Parser; +use oxc_span::SourceType; use std::{fs, path::Path}; -// (from, to) -const RENAMED_MODULES: &[(&str, &str)] = &[("tauri", "core"), ("window", "webviewWindow")]; -const PLUGINIFIED_MODULES: &[&str] = &[ +const RENAMED_MODULES: phf::Map<&str, &str> = phf::phf_map! { + "tauri" => "core", + "window" => "webviewWindow" +}; +const PLUGINIFIED_MODULES: [&str; 11] = [ "cli", "clipboard", "dialog", @@ -25,6 +33,24 @@ const PLUGINIFIED_MODULES: &[&str] = &[ "shell", "updater", ]; +// (from, to) +const MODULES_MAP: phf::Map<&str, &str> = phf::phf_map! { + // renamed + "@tauri-apps/api/tauri" => "@tauri-apps/api/core", + "@tauri-apps/api/window" => "@tauri-apps/api/webviewWindow", + // pluginified + "@tauri-apps/api/cli" => "@tauri-apps/plugin-cli", + "@tauri-apps/api/clipboard" => "@tauri-apps/plugin-clipboard-manager", + "@tauri-apps/api/dialog" => "@tauri-apps/plugin-dialog", + "@tauri-apps/api/fs" => "@tauri-apps/plugin-fs", + "@tauri-apps/api/globalShortcut" => "@tauri-apps/plugin-global-shortcut", + "@tauri-apps/api/http" => "@tauri-apps/plugin-http", + "@tauri-apps/api/notification" => "@tauri-apps/plugin-notification", + "@tauri-apps/api/os" => "@tauri-apps/plugin-os", + "@tauri-apps/api/process" => "@tauri-apps/plugin-process", + "@tauri-apps/api/shell" => "@tauri-apps/plugin-shell", + "@tauri-apps/api/updater" => "@tauri-apps/plugin-updater", +}; const JS_EXTENSIONS: &[&str] = &["js", "mjs", "jsx", "ts", "mts", "tsx"]; /// Returns a list of paths that could not be migrated @@ -37,45 +63,18 @@ pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> { .next() .unwrap_or(PackageManager::Npm); - let tauri_api_import_regex = regex::bytes::Regex::new(r"@tauri-apps/api/(\w+)").unwrap(); - for entry in walk_builder(app_dir).build().flatten() { if entry.file_type().map(|t| t.is_file()).unwrap_or_default() { let path = entry.path(); let ext = path.extension().unwrap_or_default(); if JS_EXTENSIONS.iter().any(|e| e == &ext) { - let js_contents = fs::read(path)?; - - let new_contents = - tauri_api_import_regex.replace_all(&js_contents, |cap: ®ex::bytes::Captures<'_>| { - let module = cap.get(1).unwrap().as_bytes(); - let mut module = String::from_utf8_lossy(module).to_string(); - let original = cap.get(0).unwrap().as_bytes(); - let original = String::from_utf8_lossy(original).to_string(); - - let new = if let Some((_, renamed_to)) = - RENAMED_MODULES.iter().find(|(from, _to)| *from == module) - { - renamed_to.to_string() - } else if PLUGINIFIED_MODULES.contains(&module.as_str()) { - match module.as_str() { - "clipboard" => module = String::from("clipboard-manager"), - "globalShortcut" => module = String::from("global-shortcut"), - _ => {} - } - let js_plugin = format!("@tauri-apps/plugin-{module}"); - let cargo_crate = format!("tauri-plugin-{module}"); - new_npm_packages.push(js_plugin.clone()); - new_cargo_packages.push(cargo_crate); - js_plugin - } else { - return original; - }; - - log::info!("Replacing `{original}` with `{new}` on {}", path.display()); - new - }); - + let js_contents = std::fs::read_to_string(path)?; + let new_contents = migrate_imports( + path, + &js_contents, + &mut new_cargo_packages, + &mut new_npm_packages, + )?; if new_contents != js_contents { fs::write(path, new_contents) .with_context(|| format!("Error writing {}", path.display()))?; @@ -84,11 +83,15 @@ pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> { } } + new_npm_packages.sort(); + new_npm_packages.dedup(); if !new_npm_packages.is_empty() { pm.install(&new_npm_packages) .context("Error installing new npm packages")?; } + new_cargo_packages.sort(); + new_cargo_packages.dedup(); if !new_cargo_packages.is_empty() { cargo::install(&new_cargo_packages, Some(tauri_dir)) .context("Error installing new Cargo packages")?; @@ -96,3 +99,373 @@ pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> { Ok(()) } + +fn migrate_imports<'a>( + path: &'a Path, + js_source: &'a str, + new_cargo_packages: &mut Vec, + new_npm_packages: &mut Vec, +) -> crate::Result { + let mut magic_js_source = MagicString::new(js_source); + + let source_type = SourceType::from_path(path).unwrap(); + let allocator = Allocator::default(); + let ret = Parser::new(&allocator, js_source, source_type).parse(); + if !ret.errors.is_empty() { + anyhow::bail!( + "failed to parse {} as valid Javascript/Typescript file", + path.display() + ) + } + + let mut program = ret.program; + + let mut stmts_to_add = Vec::new(); + let mut imports_to_add = Vec::new(); + + for import in program.body.iter_mut() { + if let Statement::ImportDeclaration(stmt) = import { + let module = stmt.source.value.as_str(); + + // skip parsing non @tauri-apps/api imports + if !module.starts_with("@tauri-apps/api") { + continue; + } + + // convert module to its pluginfied module or renamed one + // import { ... } from "@tauri-apps/api/window" -> import { ... } from "@tauri-apps/api/webviewWindow" + // import { ... } from "@tauri-apps/api/cli" -> import { ... } from "@tauri-apps/plugin-cli" + if let Some(&module) = MODULES_MAP.get(module) { + // +1 and -1, to skip modifying the import quotes + magic_js_source + .overwrite( + stmt.source.span.start as i64 + 1, + stmt.source.span.end as i64 - 1, + module, + Default::default(), + ) + .map_err(|e| anyhow::anyhow!("{e}")) + .context("failed to replace import source")?; + + // if module was pluginified, add to packages + let module = module.split_once("plugin-"); + if let Some((_, module)) = module { + let js_plugin = format!("@tauri-apps/plugin-{module}"); + let cargo_crate = format!("tauri-plugin-{module}"); + new_npm_packages.push(js_plugin); + new_cargo_packages.push(cargo_crate); + } + } + + let Some(specifiers) = &mut stmt.specifiers else { + continue; + }; + + for specifier in specifiers.iter() { + if let ImportDeclarationSpecifier::ImportSpecifier(specifier) = specifier { + let new_identifier = match specifier.imported.name().as_str() { + // migrate appWindow from: + // ``` + // import { appWindow } from "@tauri-apps/api/window" + // ``` + // to: + // ``` + // import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow" + // const appWindow = getCurrentWebviewWindow() + // ``` + "appWindow" if module == "@tauri-apps/api/window" => { + stmts_to_add.push("\nconst appWindow = getCurrentWebviewWindow()"); + Some("getCurrentWebviewWindow") + } + + // migrate pluginified modules from: + // ``` + // import { dialog, cli as superCli } from "@tauri-apps/api" + // ``` + // to: + // ``` + // import dialog from "@tauri-apps/plugin-dialog" + // import cli as superCli from "@tauri-apps/plugin-cli" + // ``` + import if PLUGINIFIED_MODULES.contains(&import) && module == "@tauri-apps/api" => { + let js_plugin: &str = MODULES_MAP[&format!("@tauri-apps/api/{import}")]; + let (_, plugin_name) = js_plugin.split_once("plugin-").unwrap(); + let cargo_crate = format!("tauri-plugin-{plugin_name}"); + new_npm_packages.push(js_plugin.to_string()); + new_cargo_packages.push(cargo_crate); + + if specifier.local.name.as_str() != import { + let local = &specifier.local.name; + imports_to_add.push(format!("\nimport {import} as {local} from \"{js_plugin}\"")); + } else { + imports_to_add.push(format!("\nimport {import} from \"{js_plugin}\"")); + }; + None + } + + import if module == "@tauri-apps/api" => match RENAMED_MODULES.get(import) { + Some(m) => Some(*m), + None => continue, + }, + + // nothing to do, go to next specifier + _ => continue, + }; + + // if identifier was renamed, it will be Some() + // and so we convert the import + // import { appWindow } from "@tauri-apps/api/window" -> import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow" + if let Some(new_identifier) = new_identifier { + magic_js_source + .overwrite( + specifier.span.start as _, + specifier.span.end as _, + new_identifier, + Default::default(), + ) + .map_err(|e| anyhow::anyhow!("{e}")) + .context("failed to rename identifier")?; + } else { + // if None, we need to remove this specifier, + // it will also be replaced with an import from its new plugin below + + // find the next comma or the bracket ending the import + let start = specifier.span.start as usize; + let sliced = &js_source[start..]; + let comma_or_bracket = sliced.chars().find_position(|&c| c == ',' || c == '}'); + let end = match comma_or_bracket { + Some((n, ',')) => n + start + 1, + Some((_, '}')) => specifier.span.end as _, + _ => continue, + }; + + magic_js_source + .remove(start as _, end as _) + .map_err(|e| anyhow::anyhow!("{e}")) + .context("failed to remove identifier")?; + } + } + } + } + } + + // find the end of import list + // fallback to the program start + let start = program + .body + .iter() + .rev() + .find(|s| matches!(s, Statement::ImportDeclaration(_))) + .map(|s| match s { + Statement::ImportDeclaration(s) => s.span.end, + _ => unreachable!(), + }) + .unwrap_or(program.span.start); + + if !imports_to_add.is_empty() { + for import in imports_to_add { + magic_js_source + .append_right(start as _, &import) + .map_err(|e| anyhow::anyhow!("{e}")) + .context("failed to add import")?; + } + } + + if !stmts_to_add.is_empty() { + for stmt in stmts_to_add { + magic_js_source + .append_right(start as _, stmt) + .map_err(|e| anyhow::anyhow!("{e}")) + .context("failed to add statement")?; + } + } + + Ok(magic_js_source.to_string()) +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn migrates() { + let input = r#" +import { useState } from "react"; +import reactLogo from "./assets/react.svg"; +import { invoke, dialog, cli as superCli } from "@tauri-apps/api"; +import { appWindow } from "@tauri-apps/api/window"; +import { convertFileSrc } from "@tauri-apps/api/tauri"; +import { open } from "@tauri-apps/api/dialog"; +import { register } from "@tauri-apps/api/globalShortcut"; +import clipboard from "@tauri-apps/api/clipboard"; +import * as fs from "@tauri-apps/api/fs"; +import "./App.css"; + +function App() { + const [greetMsg, setGreetMsg] = useState(""); + const [name, setName] = useState(""); + + async function greet() { + // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command + setGreetMsg(await invoke("greet", { name })); + await open(); + await dialog.save(); + await convertFileSrc(""); + const a = appWindow.label; + superCli.getMatches(); + clipboard.readText(); + fs.exists(""); + } + + return ( +
+

Welcome to Tauri!

+ +
+ + Vite logo + + + Tauri logo + + + React logo + +
+ +

Click on the Tauri, Vite, and React logos to learn more.

+ +
{ + e.preventDefault(); + greet(); + }} + > + setName(e.currentTarget.value)} + placeholder="Enter a name..." + /> + +
+ +

{greetMsg}

+
+ ); +} + +export default App; +"#; + + let expected = r#" +import { useState } from "react"; +import reactLogo from "./assets/react.svg"; +import { invoke, } from "@tauri-apps/api"; +import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"; +import { convertFileSrc } from "@tauri-apps/api/core"; +import { open } from "@tauri-apps/plugin-dialog"; +import { register } from "@tauri-apps/plugin-global-shortcut"; +import clipboard from "@tauri-apps/plugin-clipboard-manager"; +import * as fs from "@tauri-apps/plugin-fs"; +import "./App.css"; +import dialog from "@tauri-apps/plugin-dialog" +import cli as superCli from "@tauri-apps/plugin-cli" +const appWindow = getCurrentWebviewWindow() + +function App() { + const [greetMsg, setGreetMsg] = useState(""); + const [name, setName] = useState(""); + + async function greet() { + // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command + setGreetMsg(await invoke("greet", { name })); + await open(); + await dialog.save(); + await convertFileSrc(""); + const a = appWindow.label; + superCli.getMatches(); + clipboard.readText(); + fs.exists(""); + } + + return ( +
+

Welcome to Tauri!

+ +
+ + Vite logo + + + Tauri logo + + + React logo + +
+ +

Click on the Tauri, Vite, and React logos to learn more.

+ +
{ + e.preventDefault(); + greet(); + }} + > + setName(e.currentTarget.value)} + placeholder="Enter a name..." + /> + +
+ +

{greetMsg}

+
+ ); +} + +export default App; +"#; + + let mut new_cargo_packages = Vec::new(); + let mut new_npm_packages = Vec::new(); + + let migrated = migrate_imports( + Path::new("file.js"), + input, + &mut new_cargo_packages, + &mut new_npm_packages, + ) + .unwrap(); + + assert_eq!(migrated, expected); + + assert_eq!( + new_cargo_packages, + vec![ + "tauri-plugin-dialog", + "tauri-plugin-cli", + "tauri-plugin-dialog", + "tauri-plugin-global-shortcut", + "tauri-plugin-clipboard-manager", + "tauri-plugin-fs" + ] + ); + + assert_eq!( + new_npm_packages, + vec![ + "@tauri-apps/plugin-dialog", + "@tauri-apps/plugin-cli", + "@tauri-apps/plugin-dialog", + "@tauri-apps/plugin-global-shortcut", + "@tauri-apps/plugin-clipboard-manager", + "@tauri-apps/plugin-fs" + ] + ); + } +} diff --git a/tooling/cli/src/mobile/ios/xcode_script.rs b/tooling/cli/src/mobile/ios/xcode_script.rs index 881b44b8954b..1db57517d7e4 100644 --- a/tooling/cli/src/mobile/ios/xcode_script.rs +++ b/tooling/cli/src/mobile/ios/xcode_script.rs @@ -202,7 +202,7 @@ pub fn command(options: Options) -> Result<()> { let lib_path = out_dir.join(format!("lib{}.a", config.app().lib_name())); if !lib_path.exists() { - return Err(anyhow::anyhow!("Library not found at {}. Make sure your Cargo.toml file has a [lib] block with `crate-type = [\"staticlib\", \"cdylib\", \"rlib\"]`", lib_path.display())); + return Err(anyhow::anyhow!("Library not found at {}. Make sure your Cargo.toml file has a [lib] block with `crate-type = [\"staticlib\", \"cdylib\", \"lib\"]`", lib_path.display())); } let project_dir = config.project_dir(); diff --git a/tooling/cli/templates/app/src-tauri/Cargo.crate-manifest b/tooling/cli/templates/app/src-tauri/Cargo.crate-manifest index 0b8303f30969..8f1429d35f9c 100755 --- a/tooling/cli/templates/app/src-tauri/Cargo.crate-manifest +++ b/tooling/cli/templates/app/src-tauri/Cargo.crate-manifest @@ -12,7 +12,7 @@ rust-version = "1.70" [lib] name = "app_lib" -crate-type = ["staticlib", "cdylib", "rlib"] +crate-type = ["staticlib", "cdylib", "lib"] [build-dependencies] tauri-build = {{ tauri_build_dep }} diff --git a/tooling/cli/templates/plugin/__example-api/tauri-app/src-tauri/Cargo.crate-manifest b/tooling/cli/templates/plugin/__example-api/tauri-app/src-tauri/Cargo.crate-manifest index 325d84cc9d6f..48ec559988f2 100644 --- a/tooling/cli/templates/plugin/__example-api/tauri-app/src-tauri/Cargo.crate-manifest +++ b/tooling/cli/templates/plugin/__example-api/tauri-app/src-tauri/Cargo.crate-manifest @@ -10,7 +10,7 @@ rust-version = "1.70" [lib] name = "tauri_app_lib" -crate-type = ["staticlib", "cdylib", "rlib"] +crate-type = ["staticlib", "cdylib", "lib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tooling/cli/templates/plugin/__example-basic/vanilla/src-tauri/Cargo.crate-manifest b/tooling/cli/templates/plugin/__example-basic/vanilla/src-tauri/Cargo.crate-manifest index 325d84cc9d6f..48ec559988f2 100644 --- a/tooling/cli/templates/plugin/__example-basic/vanilla/src-tauri/Cargo.crate-manifest +++ b/tooling/cli/templates/plugin/__example-basic/vanilla/src-tauri/Cargo.crate-manifest @@ -10,7 +10,7 @@ rust-version = "1.70" [lib] name = "tauri_app_lib" -crate-type = ["staticlib", "cdylib", "rlib"] +crate-type = ["staticlib", "cdylib", "lib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html