From 88af3e7eff31e919257133e7715f57611b1b252c Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 17 Jul 2024 19:11:18 -0700 Subject: [PATCH] Hotreloading of `for/if/body`, formatted strings, literals, component props, nested rsx, light CLI rewrite, cli TUI (#2258) Hotreload the contents of for loops, if chains, component bodies, props, attributes, and any literals discovered in rsx! Add a TUI renderer to the CLI. Improve the CLI build system to be async and parallel. Refactor RSX to allow partial expansion of expressions. Merge autofmt implementations for consistency. Merge the representation of elements and components under the hood. Add a diagnostics system for rsx for improved error messages. Drop interprocess and move to websockets for communication between the CLI and the server. Assign IDs to nodes and attributes in a stable way to be used in non compiler contexts. Add hotreloading to any body of component/for loop/if chain/etc. --------- Co-authored-by: Evan Almloff Co-authored-by: Liam Mitchell --- .github/workflows/main.yml | 10 +- Cargo.lock | 1370 ++++++++++------- Cargo.toml | 23 +- examples/rsx_usage.rs | 2 +- packages/autofmt/src/buffer.rs | 4 +- packages/autofmt/src/collect_macros.rs | 14 +- packages/autofmt/src/component.rs | 291 ---- packages/autofmt/src/expr.rs | 58 - packages/autofmt/src/lib.rs | 102 +- .../autofmt/src/{element.rs => rsx_block.rs} | 305 ++-- packages/autofmt/src/writer.rs | 279 ++-- packages/autofmt/tests/samples/attributes.rsx | 2 +- .../autofmt/tests/samples/commentshard.rsx | 6 +- packages/autofmt/tests/samples/docsite.rsx | 6 +- packages/autofmt/tests/samples/long.rsx | 2 +- packages/autofmt/tests/samples/misplaced.rsx | 6 +- .../autofmt/tests/wrong/multiexpr-4sp.rsx | 2 +- .../autofmt/tests/wrong/multiexpr-tab.rsx | 2 +- packages/cli-config/Cargo.toml | 9 +- packages/cli-config/src/cargo.rs | 115 -- packages/cli-config/src/config.rs | 462 +----- packages/cli-config/src/lib.rs | 26 +- packages/cli-config/src/serve.rs | 79 +- packages/cli/Cargo.toml | 52 +- packages/cli/{src => }/assets/dioxus.toml | 6 - packages/cli/{src => }/assets/icon.ico | Bin packages/cli/assets/index.html | 12 + packages/cli/assets/loading.html | 118 ++ packages/cli/build.rs | 3 + packages/cli/src/assets.rs | 84 +- packages/cli/src/assets/index.html | 38 - packages/cli/src/builder.rs | 847 ---------- packages/cli/src/builder/cargo.rs | 224 +++ packages/cli/src/builder/fullstack.rs | 109 ++ packages/cli/src/builder/mod.rs | 145 ++ packages/cli/src/builder/prepare_html.rs | 132 ++ packages/cli/src/builder/progress.rs | 273 ++++ packages/cli/src/builder/web.rs | 182 +++ packages/cli/src/cli/build.rs | 232 +-- packages/cli/src/cli/bundle.rs | 104 +- packages/cli/src/cli/cfg.rs | 218 --- packages/cli/src/cli/check.rs | 48 +- packages/cli/src/cli/clean.rs | 15 +- packages/cli/src/cli/config.rs | 25 +- packages/cli/src/cli/create.rs | 2 +- packages/cli/src/cli/mod.rs | 18 +- packages/cli/src/cli/plugin.rs | 39 - packages/cli/src/cli/serve.rs | 155 +- packages/cli/src/cli/translate.rs | 7 +- packages/cli/src/dioxus_crate.rs | 370 +++++ packages/cli/src/dx_build_info.rs | 2 + packages/cli/src/error.rs | 14 +- packages/cli/src/lib.rs | 17 +- packages/cli/src/main.rs | 116 +- packages/cli/src/metadata.rs | 64 + packages/cli/src/plugin/interface/command.rs | 65 - packages/cli/src/plugin/interface/dirs.rs | 13 - packages/cli/src/plugin/interface/fs.rs | 85 - packages/cli/src/plugin/interface/log.rs | 27 - packages/cli/src/plugin/interface/mod.rs | 233 --- packages/cli/src/plugin/interface/network.rs | 27 - packages/cli/src/plugin/interface/os.rs | 18 - packages/cli/src/plugin/interface/path.rs | 41 - packages/cli/src/plugin/mod.rs | 328 ---- packages/cli/src/plugin/types.rs | 138 -- packages/cli/src/serve/builder.rs | 167 ++ .../src/serve}/hot_reloading_file_map.rs | 222 +-- packages/cli/src/serve/logs_tab.rs | 1 + packages/cli/src/serve/mod.rs | 157 ++ packages/cli/src/serve/output.rs | 841 ++++++++++ .../cli/src/{server/web => serve}/proxy.rs | 78 +- packages/cli/src/serve/server.rs | 587 +++++++ packages/cli/src/serve/watcher.rs | 283 ++++ packages/cli/src/server/desktop/mod.rs | 373 ----- packages/cli/src/server/fullstack/mod.rs | 172 --- packages/cli/src/server/mod.rs | 361 ----- packages/cli/src/server/output.rs | 157 -- packages/cli/src/server/web/mod.rs | 219 --- packages/cli/src/server/web/server.rs | 206 --- packages/{cli-config => cli}/src/settings.rs | 17 +- packages/core-macro/Cargo.toml | 3 +- .../tests/rsx/trailing-comma-0.stderr | 7 +- packages/core/src/diff/mod.rs | 53 +- packages/core/src/diff/node.rs | 28 +- packages/core/src/hotreload_utils.rs | 73 + packages/core/src/lib.rs | 20 +- packages/core/src/nodes.rs | 20 +- packages/core/src/properties.rs | 2 +- packages/core/src/reactive_context.rs | 3 + packages/core/src/runtime.rs | 6 +- packages/core/src/virtual_dom.rs | 23 +- packages/core/tests/diff_component.rs | 7 +- packages/core/tests/hotreload.rs | 38 + packages/core/tests/kitchen_sink.rs | 14 +- packages/desktop/Cargo.toml | 19 +- packages/desktop/src/app.rs | 87 +- packages/desktop/src/assets.rs | 5 +- packages/desktop/src/config.rs | 2 +- packages/desktop/src/ipc.rs | 2 +- packages/desktop/src/protocol.rs | 2 +- packages/dioxus/README.md | 2 +- packages/dioxus/src/launch.rs | 1 + packages/dioxus/src/lib.rs | 2 +- packages/fullstack/Cargo.toml | 2 +- .../fullstack/examples/axum-auth/Cargo.toml | 1 - .../examples/axum-hello-world/Cargo.toml | 4 +- .../fullstack/examples/hackernews/Cargo.toml | 4 +- packages/fullstack/src/axum_adapter.rs | 6 - packages/fullstack/src/launch.rs | 32 +- packages/fullstack/src/render.rs | 11 - packages/fullstack/src/serve_config.rs | 6 +- packages/hot-reload/Cargo.toml | 25 +- packages/hot-reload/src/assets/autoreload.js | 40 - packages/hot-reload/src/client.rs | 43 + packages/hot-reload/src/file_watcher.rs | 397 ----- packages/hot-reload/src/lib.rs | 145 +- packages/hot-reload/src/websocket.rs | 288 ---- packages/hot-reload/src/ws_receiver.rs | 73 + packages/html-internal-macro/Cargo.toml | 2 +- packages/interpreter/build.rs | 1 + packages/interpreter/src/js/hash.txt | 2 +- packages/interpreter/src/js/patch_console.js | 1 + packages/interpreter/src/lib.rs | 5 + packages/interpreter/src/ts/patch_console.ts | 54 + packages/liveview/Cargo.toml | 2 +- packages/liveview/src/config.rs | 2 +- packages/liveview/src/pool.rs | 16 +- packages/rsx-rosetta/Cargo.toml | 2 +- packages/rsx-rosetta/src/lib.rs | 111 +- packages/rsx/Cargo.toml | 15 +- packages/rsx/README.md | 39 + packages/rsx/src/attribute.rs | 989 ++++++------ packages/rsx/src/component.rs | 711 +++++---- packages/rsx/src/context.rs | 431 ------ packages/rsx/src/diagnostics.rs | 55 + packages/rsx/src/element.rs | 906 ++++++----- packages/rsx/src/error.rs | 67 - packages/rsx/src/errors.rs | 42 - packages/rsx/src/expr_node.rs | 46 + packages/rsx/src/forloop.rs | 69 + .../rsx/src/hot_reload/hot_reload_diff.rs | 742 ++------- packages/rsx/src/hot_reload/mod.rs | 5 - packages/rsx/src/hotreload.rs | 429 ++++++ packages/rsx/src/ifchain.rs | 169 ++ packages/rsx/src/ifmt.rs | 289 +++- packages/rsx/src/lib.rs | 203 +-- packages/rsx/src/literal.rs | 304 ++++ packages/rsx/src/location.rs | 44 +- packages/rsx/src/node.rs | 628 +++----- packages/rsx/src/partial_closure.rs | 320 ++++ packages/rsx/src/raw_expr.rs | 86 ++ packages/rsx/src/reload_stack.rs | 113 ++ packages/rsx/src/renderer.rs | 139 -- packages/rsx/src/rsx_block.rs | 543 +++++++ packages/rsx/src/rsx_call.rs | 152 ++ packages/rsx/src/scoring.rs | 323 ++++ packages/rsx/src/sub_templates.rs | 45 - packages/rsx/src/template_body.rs | 381 +++++ packages/rsx/src/text_node.rs | 115 ++ packages/rsx/src/util.rs | 34 +- packages/rsx/tests/hotreload_pattern.rs | 830 +++++++++- packages/rsx/tests/hotreloads.rs | 22 +- .../rsx/tests/invalid/changedexpr.new.rsx | 9 - .../rsx/tests/invalid/changedexpr.old.rsx | 11 - packages/rsx/tests/parsing.rs | 168 +- packages/rsx/tests/rsx_diff.rs | 112 -- .../snapshots/rsx_diff__create_template.snap | 64 - .../snapshots/rsx_diff__diff_template-2.snap | 81 - .../snapshots/rsx_diff__diff_template.snap | 85 - packages/rsx/tests/valid/combo.new.rsx | 22 +- packages/server-macro/Cargo.toml | 2 +- packages/signals/src/global/memo.rs | 17 +- packages/signals/src/global/mod.rs | 99 +- packages/signals/src/global/signal.rs | 41 +- packages/signals/src/signal.rs | 3 + packages/ssr/tests/hydration.rs | 2 +- packages/static-generation/src/launch.rs | 3 +- packages/static-generation/src/ssg.rs | 2 +- packages/web/Cargo.toml | 3 + packages/web/src/hot_reload.rs | 184 ++- packages/web/src/lib.rs | 11 +- 181 files changed, 12755 insertions(+), 10806 deletions(-) delete mode 100644 packages/autofmt/src/component.rs delete mode 100644 packages/autofmt/src/expr.rs rename packages/autofmt/src/{element.rs => rsx_block.rs} (59%) delete mode 100644 packages/cli-config/src/cargo.rs rename packages/cli/{src => }/assets/dioxus.toml (95%) rename packages/cli/{src => }/assets/icon.ico (100%) create mode 100644 packages/cli/assets/index.html create mode 100644 packages/cli/assets/loading.html create mode 100644 packages/cli/build.rs delete mode 100644 packages/cli/src/assets/index.html delete mode 100644 packages/cli/src/builder.rs create mode 100644 packages/cli/src/builder/cargo.rs create mode 100644 packages/cli/src/builder/fullstack.rs create mode 100644 packages/cli/src/builder/mod.rs create mode 100644 packages/cli/src/builder/prepare_html.rs create mode 100644 packages/cli/src/builder/progress.rs create mode 100644 packages/cli/src/builder/web.rs delete mode 100644 packages/cli/src/cli/cfg.rs delete mode 100644 packages/cli/src/cli/plugin.rs create mode 100644 packages/cli/src/dioxus_crate.rs create mode 100644 packages/cli/src/dx_build_info.rs create mode 100644 packages/cli/src/metadata.rs delete mode 100644 packages/cli/src/plugin/interface/command.rs delete mode 100644 packages/cli/src/plugin/interface/dirs.rs delete mode 100644 packages/cli/src/plugin/interface/fs.rs delete mode 100644 packages/cli/src/plugin/interface/log.rs delete mode 100644 packages/cli/src/plugin/interface/mod.rs delete mode 100644 packages/cli/src/plugin/interface/network.rs delete mode 100644 packages/cli/src/plugin/interface/os.rs delete mode 100644 packages/cli/src/plugin/interface/path.rs delete mode 100644 packages/cli/src/plugin/mod.rs delete mode 100644 packages/cli/src/plugin/types.rs create mode 100644 packages/cli/src/serve/builder.rs rename packages/{rsx/src/hot_reload => cli/src/serve}/hot_reloading_file_map.rs (59%) create mode 100644 packages/cli/src/serve/logs_tab.rs create mode 100644 packages/cli/src/serve/mod.rs create mode 100644 packages/cli/src/serve/output.rs rename packages/cli/src/{server/web => serve}/proxy.rs (83%) create mode 100644 packages/cli/src/serve/server.rs create mode 100644 packages/cli/src/serve/watcher.rs delete mode 100644 packages/cli/src/server/desktop/mod.rs delete mode 100644 packages/cli/src/server/fullstack/mod.rs delete mode 100644 packages/cli/src/server/mod.rs delete mode 100644 packages/cli/src/server/output.rs delete mode 100644 packages/cli/src/server/web/mod.rs delete mode 100644 packages/cli/src/server/web/server.rs rename packages/{cli-config => cli}/src/settings.rs (87%) create mode 100644 packages/core/src/hotreload_utils.rs create mode 100644 packages/core/tests/hotreload.rs delete mode 100644 packages/hot-reload/src/assets/autoreload.js create mode 100644 packages/hot-reload/src/client.rs delete mode 100644 packages/hot-reload/src/file_watcher.rs delete mode 100644 packages/hot-reload/src/websocket.rs create mode 100644 packages/hot-reload/src/ws_receiver.rs create mode 100644 packages/interpreter/src/js/patch_console.js create mode 100644 packages/interpreter/src/ts/patch_console.ts delete mode 100644 packages/rsx/src/context.rs create mode 100644 packages/rsx/src/diagnostics.rs delete mode 100644 packages/rsx/src/error.rs delete mode 100644 packages/rsx/src/errors.rs create mode 100644 packages/rsx/src/expr_node.rs create mode 100644 packages/rsx/src/forloop.rs create mode 100644 packages/rsx/src/hotreload.rs create mode 100644 packages/rsx/src/ifchain.rs create mode 100644 packages/rsx/src/literal.rs create mode 100644 packages/rsx/src/partial_closure.rs create mode 100644 packages/rsx/src/raw_expr.rs create mode 100644 packages/rsx/src/reload_stack.rs delete mode 100644 packages/rsx/src/renderer.rs create mode 100644 packages/rsx/src/rsx_block.rs create mode 100644 packages/rsx/src/rsx_call.rs create mode 100644 packages/rsx/src/scoring.rs delete mode 100644 packages/rsx/src/sub_templates.rs create mode 100644 packages/rsx/src/template_body.rs create mode 100644 packages/rsx/src/text_node.rs delete mode 100644 packages/rsx/tests/invalid/changedexpr.new.rsx delete mode 100644 packages/rsx/tests/invalid/changedexpr.old.rsx delete mode 100644 packages/rsx/tests/rsx_diff.rs delete mode 100644 packages/rsx/tests/snapshots/rsx_diff__create_template.snap delete mode 100644 packages/rsx/tests/snapshots/rsx_diff__diff_template-2.snap delete mode 100644 packages/rsx/tests/snapshots/rsx_diff__diff_template.snap diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f76cddb41..3e1cb356c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -228,13 +228,21 @@ jobs: args: "--all --tests", } - { - target: x86_64-apple-darwin, + target: aarch64-apple-darwin, os: macos-latest, toolchain: "1.79.0", cross: false, command: "test", args: "--all --tests", } + - { + target: x86_64-apple-darwin, + os: macos-13, + toolchain: "1.79.0", + cross: false, + command: "test", + args: "--all --tests", + } - { target: aarch64-apple-ios, os: macos-latest, diff --git a/Cargo.lock b/Cargo.lock index d278f4949..779a1a06c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,6 +149,29 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "ansi-to-html" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d73c455ae09fa2223a75114789f30ad605e9e297f79537953523366c05995f5f" +dependencies = [ + "regex", + "thiserror", +] + +[[package]] +name = "ansi-to-tui" +version = "5.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428c2992b874104caf39204b05bf89eab4ceefdd4fcb26caa6759906f547f8e8" +dependencies = [ + "nom", + "ratatui", + "simdutf8", + "smallvec", + "thiserror", +] + [[package]] name = "anstream" version = "0.6.14" @@ -236,7 +259,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -258,7 +281,7 @@ dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -350,7 +373,7 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" dependencies = [ - "brotli 6.0.0", + "brotli", "flate2", "futures-core", "memchr", @@ -362,9 +385,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0" +checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" dependencies = [ "async-task", "concurrent-queue", @@ -497,14 +520,14 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] name = "async-signal" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794f185324c2f00e771cd9f1ae8b5ac68be2ca7abb129a87afd6e86d228bc54d" +checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32" dependencies = [ "async-io 2.3.3", "async-lock 3.4.0", @@ -544,6 +567,28 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "async-task" version = "4.7.1" @@ -558,7 +603,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -650,6 +695,61 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "aws-lc-rs" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a47f2fb521b70c11ce7369a6c5fa4bd6af7e5d62ec06303875bafe7c6ba245" +dependencies = [ + "aws-lc-sys", + "mirai-annotations", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2927c7af777b460b7ccd95f8b67acd7b4c04ec8896bf0c8e80ba30523cffc057" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libc", + "paste", +] + +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core 0.3.4", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", + "itoa 1.0.11", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 0.1.2", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "axum" version = "0.7.5" @@ -657,13 +757,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" dependencies = [ "async-trait", - "axum-core", + "axum-core 0.4.3", "axum-macros", "base64 0.21.7", "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "hyper 1.4.1", "hyper-util", @@ -682,7 +782,7 @@ dependencies = [ "sha1", "sync_wrapper 1.0.1", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.21.0", "tower", "tower-layer", "tower-service", @@ -695,7 +795,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "axum", + "axum 0.7.5", "axum_session", "axum_session_auth", "dioxus", @@ -711,6 +811,23 @@ dependencies = [ "tower-http", ] +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "axum-core" version = "0.4.3" @@ -721,7 +838,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", @@ -736,7 +853,7 @@ dependencies = [ name = "axum-desktop" version = "0.1.0" dependencies = [ - "axum", + "axum 0.7.5", "dioxus", "serde", "tokio", @@ -748,13 +865,13 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733" dependencies = [ - "axum", - "axum-core", + "axum 0.7.5", + "axum-core 0.4.3", "bytes", "futures-util", "headers 0.4.0", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", @@ -770,7 +887,7 @@ name = "axum-hello-world" version = "0.1.0" dependencies = [ "dioxus", - "reqwest 0.11.27", + "reqwest 0.12.5", "serde", "simple_logger", "tracing", @@ -787,14 +904,14 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] name = "axum-router" version = "0.1.0" dependencies = [ - "axum", + "axum 0.7.5", "dioxus", "serde", "tokio", @@ -810,7 +927,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "hyper 1.4.1", "hyper-util", @@ -847,7 +964,7 @@ checksum = "d0f0035d1d5a70ac279e80a75e60592023ba8b327c929c3827f6c5ed749b9f8b" dependencies = [ "aes-gcm", "async-trait", - "axum", + "axum 0.7.5", "base64 0.21.7", "bytes", "chrono", @@ -857,7 +974,7 @@ dependencies = [ "futures", "hmac", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "rand 0.8.5", "serde", "serde_json", @@ -880,14 +997,14 @@ dependencies = [ "anyhow", "async-recursion", "async-trait", - "axum-core", + "axum-core 0.4.3", "axum_session", "bytes", "chrono", "dashmap", "futures", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "serde", "tokio", "tower-layer", @@ -957,6 +1074,29 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.71", + "which", +] + [[package]] name = "bit-vec" version = "0.6.3" @@ -1050,21 +1190,10 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", "syn_derive", ] -[[package]] -name = "brotli" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19483b140a7ac7174d34b5a581b406c64f84da5409d3e09cf4fff604f9270e67" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - [[package]] name = "brotli" version = "6.0.0" @@ -1086,15 +1215,6 @@ dependencies = [ "alloc-stdlib", ] -[[package]] -name = "bstr" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" -dependencies = [ - "memchr", -] - [[package]] name = "bstr" version = "1.9.1" @@ -1111,6 +1231,9 @@ name = "built" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6a6c0b39c38fd754ac338b00a88066436389c0f029da5d37d1e01091d9b7c17" +dependencies = [ + "git2", +] [[package]] name = "bumpalo" @@ -1160,9 +1283,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" [[package]] name = "bzip2" @@ -1221,9 +1344,9 @@ dependencies = [ [[package]] name = "cargo-generate" -version = "0.20.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa7015584550945f11fdfb7af113d30e2727468ec281c1d7f28cc1019196c25d" +checksum = "9917a953205900b20fe9c77776e2a95607a177c25faefc776920d1aa1a079079" dependencies = [ "anyhow", "auth-git2", @@ -1255,7 +1378,8 @@ dependencies = [ "serde", "tempfile", "thiserror", - "toml 0.8.14", + "time", + "toml 0.8.15", "walkdir", ] @@ -1284,14 +1408,20 @@ dependencies = [ [[package]] name = "cargo_toml" -version = "0.18.0" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "802b755090e39835a4b0440fb0bbee0df7495a8b337f63db21e616f7821c7e8c" +checksum = "4895c018bb228aa6b3ba1a0285543fcb4b704734c3fb1f72afaa75aa769500c1" dependencies = [ "serde", - "toml 0.8.14", + "toml 0.8.15", ] +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + [[package]] name = "cast" version = "0.3.0" @@ -1299,14 +1429,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] -name = "cc" -version = "1.1.0" +name = "castaway" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaff6f8ce506b9773fa786672d63fc7a191ffea1be33f72bbd4aeacefca9ffc8" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324c74f2155653c90b04f25b2a47a8a631360cb908f92a772695f430c7e31052" dependencies = [ "jobserver", "libc", - "once_cell", ] [[package]] @@ -1315,6 +1453,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfb" version = "0.7.3" @@ -1406,6 +1553,17 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "2.34.0" @@ -1448,7 +1606,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -1457,6 +1615,15 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + [[package]] name = "cocoa" version = "0.25.0" @@ -1540,6 +1707,19 @@ dependencies = [ "memchr", ] +[[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.11", + "ryu", + "static_assertions", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1562,6 +1742,44 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "console-api" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257c22cd7e487dd4a13d413beabc512c5052f0bc048db0da6a84c3d8a6142fd" +dependencies = [ + "futures-core", + "prost", + "prost-types", + "tonic", + "tracing-core", +] + +[[package]] +name = "console-subscriber" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c4cc54bae66f7d9188996404abdf7fdfa23034ef8e43478c8810828abad758" +dependencies = [ + "console-api", + "crossbeam-channel", + "crossbeam-utils", + "futures-task", + "hdrhistogram", + "humantime", + "prost", + "prost-types", + "serde", + "serde_json", + "thread_local", + "tokio", + "tokio-stream", + "tonic", + "tracing", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -1650,12 +1868,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" -[[package]] -name = "convert_case" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" - [[package]] name = "convert_case" version = "0.6.0" @@ -1720,6 +1932,15 @@ dependencies = [ "libc", ] +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + [[package]] name = "cpufeatures" version = "0.2.12" @@ -1870,6 +2091,32 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.6.0", + "crossterm_winapi", + "futures-core", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.2" @@ -1933,7 +2180,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -1964,7 +2211,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" dependencies = [ "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -2019,7 +2266,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -2036,7 +2283,7 @@ checksum = "4b2c1c1776b986979be68bb2285da855f8d8a35851a769fca8740df7c3d07877" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -2060,7 +2307,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -2071,9 +2318,15 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.70", + "syn 2.0.71", ] +[[package]] +name = "dary_heap" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7762d17f1241643615821a8455a0b2c3e803784b058693d990b11f2dce25a0ca" + [[package]] name = "dashmap" version = "5.5.3" @@ -2133,7 +2386,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -2171,7 +2424,7 @@ dependencies = [ name = "dioxus" version = "0.5.2" dependencies = [ - "axum", + "axum 0.7.5", "criterion 0.3.6", "dioxus", "dioxus-config-macro", @@ -2208,7 +2461,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -2220,25 +2473,30 @@ dependencies = [ "pretty_assertions", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] name = "dioxus-cli" version = "0.5.2" dependencies = [ + "ansi-to-html", + "ansi-to-tui", "anyhow", "atty", - "axum", + "axum 0.7.5", "axum-extra", "axum-server", - "brotli 5.0.0", + "brotli", + "built", "cargo-generate", "cargo_metadata", "cargo_toml", "chrono", "clap 4.5.9", "colored 2.1.0", + "console-subscriber", + "crossterm", "ctrlc", "dioxus-autofmt", "dioxus-check", @@ -2252,37 +2510,39 @@ dependencies = [ "fern", "flate2", "fs_extra", + "futures-channel", "futures-util", "headers 0.3.9", "html_parser", "hyper 1.4.1", - "hyper-rustls 0.26.0", + "hyper-rustls 0.27.2", "hyper-util", "ignore", - "indicatif", - "interprocess-docfix", - "lazy_static", + "krates", "log", "manganis-cli-support", - "mlua", "notify", + "once_cell", "open", - "openssl", "prettyplease", + "proc-macro2", + "ratatui", "rayon", - "reqwest 0.11.27", + "reqwest 0.12.5", "rsx-rosetta", + "rustls 0.23.11", "serde", "serde_json", "subprocess", - "syn 2.0.70", + "syn 2.0.71", "tar", "tauri-bundler", "tempfile", "thiserror", "tokio", - "toml 0.8.14", - "toml_edit 0.21.1", + "tokio-stream", + "toml 0.8.15", + "toml_edit 0.22.16", "tower", "tower-http", "tracing", @@ -2306,7 +2566,7 @@ dependencies = [ "serde_json", "tauri-bundler", "tauri-utils", - "toml 0.8.14", + "toml 0.8.15", "tracing", ] @@ -2348,7 +2608,7 @@ dependencies = [ "longest-increasing-subsequence", "pretty_assertions", "rand 0.8.5", - "reqwest 0.11.27", + "reqwest 0.12.5", "rustc-hash", "rustversion", "serde", @@ -2367,12 +2627,13 @@ version = "0.5.2" dependencies = [ "convert_case 0.6.0", "dioxus", + "dioxus-html", "dioxus-rsx", "prettyplease", "proc-macro2", "quote", "rustversion", - "syn 2.0.70", + "syn 2.0.71", "tokio", "trybuild", ] @@ -2391,6 +2652,7 @@ dependencies = [ "dioxus-hot-reload", "dioxus-html", "dioxus-interpreter-js", + "dioxus-signals", "dioxus-ssr", "dunce", "exitcode", @@ -2404,7 +2666,7 @@ dependencies = [ "objc", "objc_id", "rand 0.8.5", - "reqwest 0.11.27", + "reqwest 0.12.5", "rfd", "rustc-hash", "separator", @@ -2415,6 +2677,7 @@ dependencies = [ "tao", "thiserror", "tokio", + "tokio-tungstenite 0.23.1", "tracing", "urlencoding", "webbrowser", @@ -2435,7 +2698,7 @@ dependencies = [ "http-range", "manganis", "rand 0.8.5", - "reqwest 0.11.27", + "reqwest 0.12.5", "separator", "serde", "serde_json", @@ -2458,8 +2721,8 @@ name = "dioxus-fullstack" version = "0.5.2" dependencies = [ "async-trait", - "axum", - "base64 0.21.7", + "axum 0.7.5", + "base64 0.22.1", "bytes", "ciborium", "clap 4.5.9", @@ -2518,7 +2781,7 @@ dependencies = [ "futures-channel", "futures-util", "generational-box 0.5.2", - "reqwest 0.11.27", + "reqwest 0.12.5", "rustversion", "slab", "tokio", @@ -2530,21 +2793,22 @@ dependencies = [ name = "dioxus-hot-reload" version = "0.5.2" dependencies = [ - "axum", "chrono", + "dioxus-cli-config", "dioxus-core 0.5.2", "dioxus-html", "dioxus-rsx", + "dioxus-signals", "execute", "futures-util", "ignore", - "interprocess-docfix", "notify", "once_cell", "serde", "serde_json", "tokio", "tokio-stream", + "tokio-tungstenite 0.23.1", "tracing", ] @@ -2588,7 +2852,7 @@ dependencies = [ "convert_case 0.6.0", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", "trybuild", ] @@ -2626,7 +2890,7 @@ dependencies = [ name = "dioxus-liveview" version = "0.5.2" dependencies = [ - "axum", + "axum 0.7.5", "dioxus", "dioxus-cli-config", "dioxus-core 0.5.2", @@ -2668,7 +2932,7 @@ dependencies = [ name = "dioxus-playwright-liveview-test" version = "0.0.1" dependencies = [ - "axum", + "axum 0.7.5", "dioxus", "dioxus-liveview", "tokio", @@ -2707,7 +2971,7 @@ dependencies = [ name = "dioxus-router" version = "0.5.2" dependencies = [ - "axum", + "axum 0.7.5", "base64 0.21.7", "ciborium", "console_error_panic_hook", @@ -2744,7 +3008,7 @@ dependencies = [ "proc-macro2", "quote", "slab", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -2752,13 +3016,14 @@ name = "dioxus-rsx" version = "0.5.2" dependencies = [ "dioxus-core 0.5.2", - "insta", "internment", - "krates", + "prettier-please", + "prettyplease", "proc-macro2", + "proc-macro2-diagnostics", "quote", "serde", - "syn 2.0.70", + "syn 2.0.71", "tracing", ] @@ -2774,7 +3039,7 @@ dependencies = [ "once_cell", "parking_lot", "rand 0.8.5", - "reqwest 0.11.27", + "reqwest 0.12.5", "rustc-hash", "serde", "simple_logger", @@ -2815,7 +3080,7 @@ dependencies = [ name = "dioxus-static-site-generation" version = "0.5.2" dependencies = [ - "axum", + "axum 0.7.5", "dioxus", "dioxus-cli-config", "dioxus-fullstack", @@ -2847,8 +3112,10 @@ dependencies = [ "console_error_panic_hook", "dioxus", "dioxus-core 0.5.2", + "dioxus-hot-reload", "dioxus-html", "dioxus-interpreter-js", + "dioxus-signals", "dioxus-ssr", "dioxus-web", "futures-channel", @@ -2877,7 +3144,7 @@ dependencies = [ "proc-macro2", "quote", "server_fn_macro", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -2954,7 +3221,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -3026,18 +3293,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" -[[package]] -name = "enum-as-inner" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "syn 2.0.70", -] - [[package]] name = "enumflags2" version = "0.7.10" @@ -3056,7 +3311,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -3077,7 +3332,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -3208,7 +3463,7 @@ checksum = "ce8cd46a041ad005ab9c71263f9a0ff5b529eac0fe4cc9b4a20f4f0765d8cf4b" dependencies = [ "execute-command-tokens", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -3367,7 +3622,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -3550,7 +3805,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -3851,7 +4106,7 @@ version = "0.31.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b8ee65074b2bbb91d9d97c15d172ea75043aefebf9869b5b329149dc76501c" dependencies = [ - "bstr 1.9.1", + "bstr", "gix-date", "gix-utils", "itoa 1.0.11", @@ -3865,7 +4120,7 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7580e05996e893347ad04e1eaceb92e1c0e6a3ffe517171af99bf6b6df0ca6e5" dependencies = [ - "bstr 1.9.1", + "bstr", "gix-config-value", "gix-features", "gix-glob", @@ -3887,7 +4142,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbd06203b1a9b33a78c88252a625031b094d9e1b647260070c25b09910c0a804" dependencies = [ "bitflags 2.6.0", - "bstr 1.9.1", + "bstr", "gix-path", "libc", "thiserror", @@ -3899,7 +4154,7 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9eed6931f21491ee0aeb922751bd7ec97b4b2fe8fbfedcb678e2a2dce5f3b8c0" dependencies = [ - "bstr 1.9.1", + "bstr", "itoa 1.0.11", "thiserror", "time", @@ -3937,7 +4192,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a29ad0990cf02c48a7aac76ed0dbddeb5a0d070034b83675cc3bbf937eace4" dependencies = [ "bitflags 2.6.0", - "bstr 1.9.1", + "bstr", "gix-features", "gix-path", ] @@ -3969,7 +4224,7 @@ version = "0.42.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25da2f46b4e7c2fa7b413ce4dffb87f69eaf89c2057e386491f4c55cadbfe386" dependencies = [ - "bstr 1.9.1", + "bstr", "gix-actor", "gix-date", "gix-features", @@ -3988,7 +4243,7 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca987128ffb056d732bd545db5db3d8b103d252fbf083c2567bb0796876619a4" dependencies = [ - "bstr 1.9.1", + "bstr", "gix-trace", "home", "once_cell", @@ -4064,7 +4319,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82c27dd34a49b1addf193c92070bcbf3beaf6e10f16a78544de6372e146a0acf" dependencies = [ - "bstr 1.9.1", + "bstr", "thiserror", ] @@ -4102,7 +4357,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -4145,7 +4400,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" dependencies = [ "aho-corasick", - "bstr 1.9.1", + "bstr", "log", "regex-automata 0.4.7", "regex-syntax 0.8.4", @@ -4413,7 +4668,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -4512,6 +4767,19 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "base64 0.21.7", + "byteorder", + "flate2", + "nom", + "num-traits", +] + [[package]] name = "headers" version = "0.3.9" @@ -4611,51 +4879,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hickory-proto" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07698b8420e2f0d6447a436ba999ec85d8fbf2a398bbd737b82cac4a2e96e512" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.4.0", - "ipnet", - "once_cell", - "rand 0.8.5", - "thiserror", - "tinyvec", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "hickory-resolver" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28757f23aa75c98f254cf0405e6d8c25b831b32921b050a66692427679b1f243" -dependencies = [ - "cfg-if", - "futures-util", - "hickory-proto", - "ipconfig", - "lru-cache", - "once_cell", - "parking_lot", - "rand 0.8.5", - "resolv-conf", - "smallvec", - "thiserror", - "tokio", - "tracing", -] - [[package]] name = "hkdf" version = "0.12.4" @@ -4683,17 +4906,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - [[package]] name = "html5ever" version = "0.26.0" @@ -4758,9 +4970,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http 1.1.0", @@ -4775,7 +4987,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -4844,7 +5056,7 @@ dependencies = [ "futures-util", "h2 0.4.5", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "httparse", "httpdate", "itoa 1.0.11", @@ -4868,25 +5080,6 @@ dependencies = [ "tokio-rustls 0.24.1", ] -[[package]] -name = "hyper-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" -dependencies = [ - "futures-util", - "http 1.1.0", - "hyper 1.4.1", - "hyper-util", - "log", - "rustls 0.22.4", - "rustls-native-certs", - "rustls-pki-types", - "tokio", - "tokio-rustls 0.25.0", - "tower-service", -] - [[package]] name = "hyper-rustls" version = "0.27.2" @@ -4897,11 +5090,26 @@ dependencies = [ "http 1.1.0", "hyper 1.4.1", "hyper-util", + "log", "rustls 0.23.11", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", "tower-service", + "webpki-roots 0.26.3", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.30", + "pin-project-lite", + "tokio", + "tokio-io-timeout", ] [[package]] @@ -4943,7 +5151,7 @@ dependencies = [ "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "hyper 1.4.1", "pin-project-lite", "socket2 0.5.7", @@ -4988,16 +5196,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.5.0" @@ -5067,12 +5265,12 @@ dependencies = [ [[package]] name = "image-webp" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d730b085583c4d789dfd07fdcf185be59501666a90c97c40162b37e4fdad272d" +checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" dependencies = [ "byteorder-lite", - "thiserror", + "quick-error", ] [[package]] @@ -5182,18 +5380,6 @@ dependencies = [ "generic-array 0.14.7", ] -[[package]] -name = "insta" -version = "1.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5" -dependencies = [ - "console", - "lazy_static", - "linked-hash-map", - "similar", -] - [[package]] name = "instant" version = "0.1.13" @@ -5221,35 +5407,9 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] -[[package]] -name = "interprocess-docfix" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b84ee245c606aeb0841649a9288e3eae8c61b853a8cd5c0e14450e96d53d28f" -dependencies = [ - "blocking", - "cfg-if", - "futures-core", - "futures-io", - "intmap", - "libc", - "once_cell", - "rustc_version", - "spinning", - "thiserror", - "to_method", - "winapi", -] - -[[package]] -name = "intmap" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae52f28f45ac2bc96edb7714de995cffc174a395fb0abf5bff453587c980d7b9" - [[package]] name = "inventory" version = "0.3.15" @@ -5267,18 +5427,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "ipconfig" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" -dependencies = [ - "socket2 0.5.7", - "widestring", - "windows-sys 0.48.0", - "winreg 0.50.0", -] - [[package]] name = "ipnet" version = "2.9.0" @@ -5364,6 +5512,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -5492,15 +5649,16 @@ dependencies = [ [[package]] name = "krates" -version = "0.16.10" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcb3baf2360eb25ad31f0ada3add63927ada6db457791979b82ac199f835cb9" +checksum = "9d0956eb8f64d0353d56c69544657bbf3bc143c3c0f0883cc4fa9ec1b252a404" dependencies = [ - "cargo-platform", - "cargo_metadata", + "camino", "cfg-expr", "petgraph", "semver", + "serde", + "serde_json", ] [[package]] @@ -5548,6 +5706,12 @@ dependencies = [ "spin", ] +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "leb128" version = "0.2.5" @@ -5568,21 +5732,25 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libflate" -version = "1.4.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ff4ae71b685bbad2f2f391fe74f6b7659a34871c08b210fdc039e43bee07d18" +checksum = "45d9dfdc14ea4ef0900c1cddbc8dcd553fbaacd8a4a282cf4018ae9dd04fb21e" dependencies = [ "adler32", + "core2", "crc32fast", + "dary_heap", "libflate_lz77", ] [[package]] name = "libflate_lz77" -version = "1.2.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a52d3a8bfc85f250440e4424db7d857e241a3aebbbe301f3eb606ab15c39acbf" +checksum = "e6e0d73b369f386f1c44abd9c570d5318f55ccde816ff4b562fa452e5182863d" dependencies = [ + "core2", + "hashbrown 0.14.5", "rle-decode-fast", ] @@ -5611,6 +5779,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libloading" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + [[package]] name = "libm" version = "0.2.8" @@ -5723,12 +5901,6 @@ dependencies = [ "cc", ] -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -5780,7 +5952,7 @@ checksum = "915f6d0a2963a27cd5205c1902f32ddfe3bc035816afd268cf88c0fc0f8d287e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -5841,34 +6013,6 @@ dependencies = [ "hashbrown 0.14.5", ] -[[package]] -name = "lru-cache" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" -dependencies = [ - "linked-hash-map", -] - -[[package]] -name = "lua-src" -version = "546.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da0daa7eee611a4c30c8f5ee31af55266e26e573971ba9336d2993e2da129b2" -dependencies = [ - "cc", -] - -[[package]] -name = "luajit-src" -version = "210.4.8+resty107baaf" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05167e8b2a2185758d83ed23541e5bd8bce37072e4204e0ef2c9b322bc87c4e" -dependencies = [ - "cc", - "which", -] - [[package]] name = "mac" version = "0.1.1" @@ -5907,13 +6051,13 @@ dependencies = [ [[package]] name = "manganis-cli-support" version = "0.2.4" -source = "git+https://github.com/DogeDark/dioxus-collect-assets/#ad09a549775957c5ce177274cfbb6dc76c976c10" +source = "git+https://github.com/dioxuslabs/collect-assets/#ad09a549775957c5ce177274cfbb6dc76c976c10" dependencies = [ "anyhow", "image 0.25.1", "imagequant", "lightningcss", - "manganis-common 0.2.4 (git+https://github.com/DogeDark/dioxus-collect-assets/)", + "manganis-common 0.2.4 (git+https://github.com/dioxuslabs/collect-assets/)", "mozjpeg", "object", "png", @@ -5948,7 +6092,7 @@ dependencies = [ [[package]] name = "manganis-common" version = "0.2.4" -source = "git+https://github.com/DogeDark/dioxus-collect-assets/#ad09a549775957c5ce177274cfbb6dc76c976c10" +source = "git+https://github.com/dioxuslabs/collect-assets/#ad09a549775957c5ce177274cfbb6dc76c976c10" dependencies = [ "anyhow", "base64 0.21.7", @@ -5970,7 +6114,7 @@ dependencies = [ "manganis-common 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", "tracing-subscriber", ] @@ -5988,12 +6132,6 @@ dependencies = [ "tendril", ] -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "matchers" version = "0.1.0" @@ -6110,39 +6248,10 @@ dependencies = [ ] [[package]] -name = "mlua" -version = "0.8.10" +name = "mirai-annotations" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb37b0ba91f017aa7ca2b98ef99496827770cd635b4a932a6047c5b4bbe678e" -dependencies = [ - "bstr 0.2.17", - "cc", - "futures-core", - "futures-task", - "futures-util", - "lua-src", - "luajit-src", - "mlua_derive", - "num-traits", - "once_cell", - "pkg-config", - "rustc-hash", -] - -[[package]] -name = "mlua_derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9214e60d3cf1643013b107330fcd374ccec1e4ba1eef76e7e5da5e8202e71c0" -dependencies = [ - "itertools 0.10.5", - "once_cell", - "proc-macro-error", - "proc-macro2", - "quote", - "regex", - "syn 1.0.109", -] +checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" [[package]] name = "mozjpeg" @@ -6359,21 +6468,22 @@ dependencies = [ [[package]] name = "notify" -version = "5.2.0" +version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "crossbeam-channel", "filetime", "fsevent-sys", "inotify", "kqueue", "libc", + "log", "mio", "serde", "walkdir", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -6427,7 +6537,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -6620,7 +6730,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -6629,15 +6739,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-src" -version = "300.3.1+3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91" -dependencies = [ - "cc", -] - [[package]] name = "openssl-sys" version = "0.9.102" @@ -6646,7 +6747,6 @@ checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", - "openssl-src", "pkg-config", "vcpkg", ] @@ -6786,7 +6886,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.2", + "redox_syscall 0.5.3", "smallvec", "windows-targets 0.52.6", ] @@ -6890,7 +6990,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -7018,7 +7118,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -7065,7 +7165,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -7245,6 +7345,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "prettier-please" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22020dfcf177fcc7bf5deaf7440af371400c67c0de14c399938d8ed4fb4645d3" +dependencies = [ + "proc-macro2", + "syn 2.0.71", +] + [[package]] name = "pretty_assertions" version = "1.4.0" @@ -7272,7 +7382,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -7342,6 +7452,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", + "version_check", +] + [[package]] name = "prodash" version = "28.0.0" @@ -7364,7 +7486,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" dependencies = [ "quote", - "syn 2.0.70", + "syn 2.0.71", +] + +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost", ] [[package]] @@ -7396,12 +7550,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quick-error" version = "2.0.1" @@ -7417,6 +7565,53 @@ dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.11", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf517c03a109db8100448a4be38d498df8a210a99fe0e1b9eaf39e78c640efe" +dependencies = [ + "bytes", + "rand 0.8.5", + "ring", + "rustc-hash", + "rustls 0.23.11", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46" +dependencies = [ + "libc", + "once_cell", + "socket2 0.5.7", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "1.0.36" @@ -7529,6 +7724,27 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "ratatui" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3" +dependencies = [ + "bitflags 2.6.0", + "cassowary", + "compact_str", + "crossterm", + "itertools 0.13.0", + "lru", + "paste", + "stability", + "strum 0.26.3", + "strum_macros 0.26.4", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + [[package]] name = "rav1e" version = "0.7.1" @@ -7573,7 +7789,7 @@ dependencies = [ "avif-serialize", "imgref", "loop9", - "quick-error 2.0.1", + "quick-error", "rav1e", "rayon", "rgb", @@ -7622,9 +7838,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ "bitflags 2.6.0", ] @@ -7721,7 +7937,6 @@ dependencies = [ "futures-core", "futures-util", "h2 0.3.26", - "hickory-resolver", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.30", @@ -7771,7 +7986,7 @@ dependencies = [ "futures-util", "h2 0.4.5", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "hyper 1.4.1", "hyper-rustls 0.27.2", @@ -7785,7 +8000,10 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "quinn", + "rustls 0.23.11", "rustls-pemfile 2.1.2", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", @@ -7793,24 +8011,18 @@ dependencies = [ "system-configuration", "tokio", "tokio-native-tls", + "tokio-rustls 0.26.0", + "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", + "webpki-roots 0.26.3", "winreg 0.52.0", ] -[[package]] -name = "resolv-conf" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error 1.2.3", -] - [[package]] name = "rfd" version = "0.14.1" @@ -7836,18 +8048,18 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.44" +version = "0.8.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aee83dc281d5a3200d37b299acd13b81066ea126a7f16f0eae70fc9aed241d9" +checksum = "ade4539f42266ded9e755c605bdddf546242b2c961b03b06a7375260788a0523" dependencies = [ "bytemuck", ] [[package]] name = "rhai" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6273372244d04a8a4b0bec080ea1e710403e88c5d9d83f9808b2bfa64f0982a" +checksum = "7a7d88770120601ba1e548bb6bc2a05019e54ff01b51479e38e64ec3b59d4759" dependencies = [ "ahash 0.8.11", "bitflags 2.6.0", @@ -7868,7 +8080,7 @@ checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -7966,7 +8178,7 @@ dependencies = [ name = "rsx-rosetta" version = "0.5.2" dependencies = [ - "convert_case 0.5.0", + "convert_case 0.6.0", "dioxus-autofmt", "dioxus-html", "dioxus-rsx", @@ -7974,7 +8186,7 @@ dependencies = [ "pretty_assertions", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -8053,26 +8265,13 @@ dependencies = [ "sct", ] -[[package]] -name = "rustls" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" -dependencies = [ - "log", - "ring", - "rustls-pki-types", - "rustls-webpki 0.102.5", - "subtle", - "zeroize", -] - [[package]] name = "rustls" version = "0.23.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", @@ -8136,6 +8335,7 @@ version = "0.102.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -8227,9 +8427,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", "core-foundation", @@ -8240,9 +8440,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", @@ -8340,7 +8540,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -8393,7 +8593,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -8419,9 +8619,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.8.3" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e73139bc5ec2d45e6c5fd85be5a46949c1c39a4c18e56915f5eb4c12f975e377" +checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" dependencies = [ "base64 0.22.1", "chrono", @@ -8437,14 +8637,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.8.3" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b80d3d6b56b64335c0180e5ffde23b3c5e08c14c585b51a15bd0e95393f46703" +checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -8453,7 +8653,7 @@ version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06e6e5467a2cd93ce1accfdfd8b859404f0b3b2041131ffd774fabf666b8219" dependencies = [ - "axum", + "axum 0.7.5", "bytes", "const_format", "dashmap", @@ -8492,7 +8692,7 @@ dependencies = [ "convert_case 0.6.0", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", "xxhash-rust", ] @@ -8503,7 +8703,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00783df297ec85ea605779f2fef9cbec98981dffe2e01e1a9845c102ee1f1ae6" dependencies = [ "server_fn_macro", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -8559,6 +8759,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook" version = "0.3.17" @@ -8569,6 +8775,17 @@ dependencies = [ "signal-hook-registry", ] +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -8618,12 +8835,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" -[[package]] -name = "similar" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" - [[package]] name = "simple-static-generation" version = "0.1.0" @@ -8676,7 +8887,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edc90d3e8623d29a664cd8dba5078b600dd203444f00b9739f744e4c6e7aeaf2" dependencies = [ "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -8772,15 +8983,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spinning" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d4f0e86297cad2658d92a707320d87bf4e6ae1050287f51d19b67ef3f153a7b" -dependencies = [ - "lock_api", -] - [[package]] name = "spki" version = "0.7.3" @@ -9023,6 +9225,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "stability" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +dependencies = [ + "quote", + "syn 2.0.71", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -9090,6 +9302,15 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros 0.26.4", +] + [[package]] name = "strum_macros" version = "0.24.3" @@ -9103,6 +9324,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.71", +] + [[package]] name = "subprocess" version = "0.2.9" @@ -9151,9 +9385,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.70" +version = "2.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" +checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" dependencies = [ "proc-macro2", "quote", @@ -9169,7 +9403,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -9214,7 +9448,7 @@ dependencies = [ "cfg-expr", "heck 0.5.0", "pkg-config", - "toml 0.8.14", + "toml 0.8.15", "version-compare", ] @@ -9292,9 +9526,9 @@ checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2" [[package]] name = "tauri-bundler" -version = "1.4.0" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c08935ca08f698ea9fc1038322b95f0a2834a7864d7e6ef62ba619ae5b51876" +checksum = "f4693930d30c4836ef41f51097502fd77a0ae9f54c3af6aafb8997b2a81f40b4" dependencies = [ "anyhow", "ar", @@ -9308,7 +9542,6 @@ dependencies = [ "libflate", "log", "md5", - "native-tls", "os_pipe", "plist", "regex", @@ -9328,7 +9561,7 @@ dependencies = [ "uuid", "walkdir", "windows-sys 0.48.0", - "winreg 0.50.0", + "winreg 0.51.0", "zip", ] @@ -9434,22 +9667,22 @@ checksum = "a38c90d48152c236a3ab59271da4f4ae63d678c5d7ad6b7714d7cb9760be5e4b" [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -9540,17 +9773,11 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "to_method" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" - [[package]] name = "tokio" -version = "1.38.0" +version = "1.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" dependencies = [ "backtrace", "bytes", @@ -9562,9 +9789,20 @@ dependencies = [ "signal-hook-registry", "socket2 0.5.7", "tokio-macros", + "tracing", "windows-sys 0.48.0", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.3.0" @@ -9573,7 +9811,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -9596,17 +9834,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" -dependencies = [ - "rustls 0.22.4", - "rustls-pki-types", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.0" @@ -9639,7 +9866,22 @@ dependencies = [ "futures-util", "log", "tokio", - "tungstenite", + "tungstenite 0.21.0", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +dependencies = [ + "futures-util", + "log", + "native-tls", + "rustls 0.23.11", + "tokio", + "tokio-native-tls", + "tungstenite 0.23.0", ] [[package]] @@ -9671,15 +9913,15 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" dependencies = [ "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.15", + "toml_edit 0.22.16", ] [[package]] @@ -9728,9 +9970,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.15" +version = "0.22.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59a3a72298453f564e2b111fa896f8d07fabb36f51f06d7e875fc5e0b5a3ef1" +checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" dependencies = [ "indexmap 2.2.6", "serde", @@ -9739,6 +9981,33 @@ dependencies = [ "winnow 0.6.13", ] +[[package]] +name = "tonic" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" +dependencies = [ + "async-stream", + "async-trait", + "axum 0.6.20", + "base64 0.21.7", + "bytes", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.4.13" @@ -9747,9 +10016,13 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", + "indexmap 1.9.3", "pin-project", "pin-project-lite", + "rand 0.8.5", + "slab", "tokio", + "tokio-util", "tower-layer", "tower-service", "tracing", @@ -9768,7 +10041,7 @@ dependencies = [ "futures-core", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "http-range-header", "httpdate", @@ -9818,7 +10091,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -9910,7 +10183,7 @@ dependencies = [ "serde_derive", "serde_json", "termcolor", - "toml 0.8.14", + "toml 0.8.15", ] [[package]] @@ -9932,6 +10205,25 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror", + "utf-8", +] + [[package]] name = "twox-hash" version = "1.6.3" @@ -10013,6 +10305,17 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "unicode-width" version = "0.1.13" @@ -10055,7 +10358,6 @@ checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea" dependencies = [ "base64 0.22.1", "log", - "native-tls", "once_cell", "rustls 0.23.11", "rustls-pki-types", @@ -10070,7 +10372,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", - "idna 0.5.0", + "idna", "percent-encoding", "serde", ] @@ -10243,7 +10545,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", "wasm-bindgen-shared", ] @@ -10309,7 +10611,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10352,7 +10654,7 @@ checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -10416,8 +10718,8 @@ checksum = "2fd87a4c135535ffed86123b6fb0f0a5a0bc89e50416c942c5f0662c645f679c" dependencies = [ "anyhow", "libc", - "strum", - "strum_macros", + "strum 0.24.1", + "strum_macros 0.24.3", "tempfile", "thiserror", "wasm-opt-cxx-sys", @@ -10584,7 +10886,7 @@ checksum = "ac1345798ecd8122468840bcdf1b95e5dc6d2206c5e4b0eafa078d061f59c9bc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -10626,12 +10928,6 @@ dependencies = [ "wasite", ] -[[package]] -name = "widestring" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" - [[package]] name = "winapi" version = "0.3.9" @@ -10724,7 +11020,7 @@ checksum = "942ac266be9249c84ca862f0a164a39533dc2f6f33dc98ec89c8da99b82ea0bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -10735,7 +11031,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -10746,7 +11042,7 @@ checksum = "da33557140a288fae4e1d5f8873aaf9eb6613a9cf82c3e070223ff177f598b60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -10757,7 +11053,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -11011,6 +11307,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "937f3df7948156640f46aacef17a70db0de5917bda9c92b0f751f3a955b588fc" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "winreg" version = "0.52.0" @@ -11173,7 +11479,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", "zvariant_utils", ] @@ -11205,7 +11511,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -11213,6 +11519,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] [[package]] name = "zip" @@ -11328,7 +11648,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", "zvariant_utils", ] @@ -11340,5 +11660,5 @@ checksum = "fc242db087efc22bd9ade7aa7809e4ba828132edc312871584a6b4391bdf8786" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] diff --git a/Cargo.toml b/Cargo.toml index 2573e7d98..089b45c59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,20 +105,22 @@ wasm-bindgen-futures = "0.4.42" html_parser = "0.7.0" thiserror = "1.0.40" prettyplease = { version = "0.2.16", features = ["verbatim"] } -manganis-cli-support = { git = "https://github.com/DogeDark/dioxus-collect-assets/", features = ["html"] } +manganis-cli-support = { git = "https://github.com/dioxuslabs/collect-assets/", features = ["html"] } manganis = { version = "0.2.1" } -interprocess = { version = "1.2.2", package = "interprocess-docfix" } const_format = "0.2.32" - +cargo_toml = { version = "0.20.3" } +tauri-utils = { version = "=1.5.*" } +tauri-bundler = { version = "=1.4.*" } lru = "0.12.2" async-trait = "0.1.77" axum = "0.7.0" axum-server = { version = "0.6.0", default-features = false } tower = "0.4.13" http = "1.0.0" +notify = { version = "6.1.1" } tower-http = "0.5.2" hyper = "1.0.0" -hyper-rustls = "0.26.0" +hyper-rustls = "0.27.2" serde_json = "1.0.61" serde = "1.0.61" syn = "2.0" @@ -127,19 +129,18 @@ proc-macro2 = "1.0" axum_session = "0.12.1" axum_session_auth = "0.12.1" axum-extra = "0.9.2" -reqwest = "0.11.24" +reqwest = "0.12.5" owo-colors = "4.0.0" ciborium = "0.2.1" -base64 = "0.21.0" +base64 = "0.22.1" once_cell = "1.17.1" +uuid = "1.9.1" +convert_case = "0.6.0" +tokio-tungstenite = { version = "0.23.1" } # cli, cli-config dirs = "5.0.1" -# speed up some macros by optimizing them -[profile.dev.package.insta] -opt-level = 3 - [profile.dev.package.dioxus-core-macro] opt-level = 3 @@ -175,7 +176,7 @@ publish = false [dependencies] manganis = { workspace = true, optional = true } -reqwest = { version = "0.11.9", features = ["json"], optional = true } +reqwest = { workspace = true, features = ["json"], optional = true } http-range = { version = "0.1.5", optional = true } ciborium = { version = "0.2.1", optional = true } base64 = { version = "0.21.0", optional = true } diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index 78ec424f9..9085ac2d7 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -94,7 +94,7 @@ fn app() -> Element { } // Expressions can be used in element position too: - {rsx!(p { "More templating!" })}, + {rsx!(p { "More templating!" })} // Iterators {(0..10).map(|i| rsx!(li { "{i}" }))} diff --git a/packages/autofmt/src/buffer.rs b/packages/autofmt/src/buffer.rs index f19a7d8dc..3371302a2 100644 --- a/packages/autofmt/src/buffer.rs +++ b/packages/autofmt/src/buffer.rs @@ -8,7 +8,7 @@ use std::fmt::{Result, Write}; use dioxus_rsx::IfmtInput; -use crate::{indent::IndentOptions, write_ifmt}; +use crate::indent::IndentOptions; /// The output buffer that tracks indent and string #[derive(Debug, Default)] @@ -51,7 +51,7 @@ impl Buffer { } pub fn write_text(&mut self, text: &IfmtInput) -> Result { - write_ifmt(text, &mut self.buf) + write!(self.buf, "{}", text.to_string_with_quotes()) } } diff --git a/packages/autofmt/src/collect_macros.rs b/packages/autofmt/src/collect_macros.rs index 841af1316..86708525f 100644 --- a/packages/autofmt/src/collect_macros.rs +++ b/packages/autofmt/src/collect_macros.rs @@ -7,8 +7,15 @@ use syn::{visit::Visit, File, Macro}; type CollectedMacro<'a> = &'a Macro; -pub fn collect_from_file<'a>(file: &'a File, macros: &mut Vec>) { - MacroCollector::visit_file(&mut MacroCollector { macros }, file); +pub fn collect_from_file(file: &File) -> Vec> { + let mut macros = vec![]; + MacroCollector::visit_file( + &mut MacroCollector { + macros: &mut macros, + }, + file, + ); + macros } struct MacroCollector<'a, 'b> { @@ -46,7 +53,6 @@ pub fn byte_offset(input: &str, location: LineColumn) -> usize { fn parses_file_and_collects_rsx_macros() { let contents = include_str!("../tests/samples/long.rsx"); let parsed = syn::parse_file(contents).unwrap(); - let mut macros = vec![]; - collect_from_file(&parsed, &mut macros); + let macros = collect_from_file(&parsed); assert_eq!(macros.len(), 3); } diff --git a/packages/autofmt/src/component.rs b/packages/autofmt/src/component.rs deleted file mode 100644 index 484810e4d..000000000 --- a/packages/autofmt/src/component.rs +++ /dev/null @@ -1,291 +0,0 @@ -use crate::{ifmt_to_string, prettier_please::unparse_expr, writer::Location, Writer}; -use dioxus_rsx::*; -use quote::ToTokens; -use std::fmt::{Result, Write}; -use syn::{spanned::Spanned, AngleBracketedGenericArguments}; - -#[derive(Debug)] -enum ShortOptimization { - // Special because we want to print the closing bracket immediately - Empty, - - // Special optimization to put everything on the same line - Oneliner, - - // Optimization where children flow but props remain fixed on top - PropsOnTop, - - // The noisiest optimization where everything flows - NoOpt, -} - -impl Writer<'_> { - pub fn write_component( - &mut self, - Component { - name, - fields, - children, - manual_props, - prop_gen_args, - key, - .. - }: &Component, - ) -> Result { - self.write_component_name(name, prop_gen_args)?; - - // decide if we have any special optimizations - // Default with none, opt the cases in one-by-one - let mut opt_level = ShortOptimization::NoOpt; - - // check if we have a lot of attributes - let attr_len = self.field_len(fields, manual_props) + self.key_len(key.as_ref()); - let is_short_attr_list = attr_len < 80; - let is_small_children = self.is_short_children(children).is_some(); - - // if we have few attributes and a lot of children, place the attrs on top - if is_short_attr_list && !is_small_children { - opt_level = ShortOptimization::PropsOnTop; - } - - // even if the attr is long, it should be put on one line - if !is_short_attr_list && (fields.len() <= 1 && manual_props.is_none()) { - if children.is_empty() { - opt_level = ShortOptimization::Oneliner; - } else { - opt_level = ShortOptimization::PropsOnTop; - } - } - - // if we have few children and few attributes, make it a one-liner - if is_short_attr_list && is_small_children { - opt_level = ShortOptimization::Oneliner; - } - - // If there's nothing at all, empty optimization - if fields.is_empty() && children.is_empty() && manual_props.is_none() && key.is_none() { - opt_level = ShortOptimization::Empty; - } - - // multiline handlers bump everything down - if attr_len > 1000 || self.out.indent.split_line_attributes() { - opt_level = ShortOptimization::NoOpt; - } - - // Useful for debugging - // dbg!( - // name.to_token_stream().to_string(), - // &opt_level, - // attr_len, - // is_short_attr_list, - // is_small_children - // ); - - match opt_level { - ShortOptimization::Empty => {} - ShortOptimization::Oneliner => { - write!(self.out, " ")?; - - self.write_component_fields(fields, key.as_ref(), manual_props, true)?; - - if !children.is_empty() && !fields.is_empty() { - write!(self.out, ", ")?; - } - - for (id, child) in children.iter().enumerate() { - self.write_ident(child)?; - if id != children.len() - 1 && children.len() > 1 { - write!(self.out, ", ")?; - } - } - - write!(self.out, " ")?; - } - - ShortOptimization::PropsOnTop => { - write!(self.out, " ")?; - self.write_component_fields(fields, key.as_ref(), manual_props, true)?; - - if !children.is_empty() && !fields.is_empty() { - write!(self.out, ",")?; - } - - self.write_body_indented(children)?; - self.out.tabbed_line()?; - } - - ShortOptimization::NoOpt => { - self.write_component_fields(fields, key.as_ref(), manual_props, false)?; - - if !children.is_empty() && !fields.is_empty() { - write!(self.out, ",")?; - } - - self.write_body_indented(children)?; - self.out.tabbed_line()?; - } - } - - write!(self.out, "}}")?; - Ok(()) - } - - fn write_component_name( - &mut self, - name: &syn::Path, - generics: &Option, - ) -> Result { - let mut name = name.to_token_stream().to_string(); - name.retain(|c| !c.is_whitespace()); - - write!(self.out, "{name}")?; - - if let Some(generics) = generics { - let mut written = generics.to_token_stream().to_string(); - written.retain(|c| !c.is_whitespace()); - - write!(self.out, "{written}")?; - } - - write!(self.out, " {{")?; - - Ok(()) - } - - fn write_component_fields( - &mut self, - fields: &[ComponentField], - key: Option<&IfmtInput>, - manual_props: &Option, - sameline: bool, - ) -> Result { - let mut field_iter = fields.iter().peekable(); - - // write the key - if let Some(key) = key { - write!(self.out, "key: {}", ifmt_to_string(key))?; - if !fields.is_empty() { - write!(self.out, ",")?; - if sameline { - write!(self.out, " ")?; - } - } - } - - while let Some(field) = field_iter.next() { - if !sameline { - self.out.indented_tabbed_line().unwrap(); - } - - let name = &field.name; - match &field.content { - ContentField::ManExpr(_exp) if field.can_be_shorthand() => { - write!(self.out, "{name}")?; - } - - ContentField::ManExpr(exp) => { - let out = self.unparse_expr(exp); - let mut lines = out.split('\n').peekable(); - let first = lines.next().unwrap(); - write!(self.out, "{name}: {first}")?; - for line in lines { - self.out.new_line()?; - self.out.indented_tab()?; - write!(self.out, "{line}")?; - } - } - - ContentField::Formatted(s) => { - write!( - self.out, - "{}: {}", - name, - s.source.as_ref().unwrap().to_token_stream() - )?; - } - ContentField::Shorthand(e) => { - write!(self.out, "{}", e.to_token_stream())?; - } - } - - if field_iter.peek().is_some() || manual_props.is_some() { - write!(self.out, ",")?; - - if sameline { - write!(self.out, " ")?; - } - } - } - - if let Some(exp) = manual_props { - if !sameline { - self.out.indented_tabbed_line().unwrap(); - } - self.write_manual_props(exp)?; - } - - Ok(()) - } - - pub fn field_len( - &mut self, - fields: &[ComponentField], - manual_props: &Option, - ) -> usize { - let attr_len = fields - .iter() - .map(|field| match &field.content { - ContentField::Formatted(s) => ifmt_to_string(s).len() , - ContentField::Shorthand(e) => e.to_token_stream().to_string().len(), - ContentField::ManExpr(exp) => { - let formatted = unparse_expr(exp); - let len = if formatted.contains('\n') { - 10000 - } else { - formatted.len() - }; - self.cached_formats.insert(Location::new(exp.span().start()) , formatted); - len - }, - } + 10) - .sum::(); - - match manual_props { - Some(p) => { - let content = unparse_expr(p); - if content.len() + attr_len > 80 { - return 100000; - } - let mut lines = content.lines(); - lines.next().unwrap(); - - if lines.next().is_none() { - attr_len + content.len() - } else { - 100000 - } - } - None => attr_len, - } - } - - fn write_manual_props(&mut self, exp: &syn::Expr) -> Result { - /* - We want to normalize the expr to the appropriate indent level. - */ - - let formatted = self.unparse_expr(exp); - - let mut lines = formatted.lines(); - - let first_line = lines.next().unwrap(); - - write!(self.out, "..{first_line}")?; - for line in lines { - self.out.indented_tabbed_line()?; - write!(self.out, "{line}")?; - } - - Ok(()) - } -} diff --git a/packages/autofmt/src/expr.rs b/packages/autofmt/src/expr.rs deleted file mode 100644 index 75bd62715..000000000 --- a/packages/autofmt/src/expr.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! pretty printer for rsx! -use std::fmt::{Result, Write}; - -use proc_macro2::Span; - -use crate::{collect_macros::byte_offset, Writer}; - -impl Writer<'_> { - pub fn write_raw_expr(&mut self, placement: Span) -> Result { - /* - We want to normalize the expr to the appropriate indent level. - */ - - let start = placement.start(); - let end = placement.end(); - - // if the expr is on one line, just write it directly - if start.line == end.line { - // split counting utf8 chars - let start = byte_offset(self.raw_src, start); - let end = byte_offset(self.raw_src, end); - let row = self.raw_src[start..end].trim(); - write!(self.out, "{row}")?; - return Ok(()); - } - - // If the expr is multiline, we want to collect all of its lines together and write them out properly - // This involves unshifting the first line if it's aligned - let first_line = &self.src[start.line - 1]; - write!(self.out, "{}", &first_line[start.column..].trim_start())?; - - let prev_block_indent_level = self.out.indent.count_indents(first_line); - - for (id, line) in self.src[start.line..end.line].iter().enumerate() { - writeln!(self.out)?; - - // check if this is the last line - let line = { - if id == (end.line - start.line) - 1 { - &line[..end.column] - } else { - line - } - }; - - // trim the leading whitespace - let previous_indent = self.out.indent.count_indents(line); - let offset = previous_indent.saturating_sub(prev_block_indent_level); - let required_indent = self.out.indent_level + offset; - self.out.write_tabs(required_indent)?; - - let line = line.trim_start(); - write!(self.out, "{line}")?; - } - - Ok(()) - } -} diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index ebefbd45d..e95863368 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -2,22 +2,16 @@ #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")] #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")] -use std::fmt::{Display, Write}; - use crate::writer::*; -use collect_macros::byte_offset; -use dioxus_rsx::{BodyNode, CallBody, IfmtInput}; +use dioxus_rsx::{BodyNode, CallBody}; use proc_macro2::LineColumn; -use quote::ToTokens; -use syn::{parse::Parser, ExprMacro, MacroDelimiter}; +use syn::{parse::Parser, ExprMacro}; mod buffer; mod collect_macros; -mod component; -mod element; -mod expr; mod indent; mod prettier_please; +mod rsx_block; mod writer; pub use indent::{IndentOptions, IndentType}; @@ -55,9 +49,7 @@ pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec { let mut formatted_blocks = Vec::new(); let parsed = syn::parse_file(contents).unwrap(); - - let mut macros = vec![]; - collect_macros::collect_from_file(&parsed, &mut macros); + let macros = collect_macros::collect_from_file(&parsed); // No macros, no work to do if macros.is_empty() { @@ -86,7 +78,11 @@ pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec { .indent .count_indents(writer.src[rsx_start.line - 1]); - write_body(&mut writer, &body); + // TESTME + // If we fail to parse this macro then we have no choice to give up and return what we've got + if writer.write_rsx_call(&body.body).is_err() { + return formatted_blocks; + } // writing idents leaves the final line ended at the end of the last ident if writer.out.buf.contains('\n') { @@ -94,23 +90,15 @@ pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec { writer.out.tab().unwrap(); } - let span = match item.delimiter { - MacroDelimiter::Paren(b) => b.span, - MacroDelimiter::Brace(b) => b.span, - MacroDelimiter::Bracket(b) => b.span, - } - .join(); + let span = item.delimiter.span().join(); + let mut formatted = writer.out.buf.split_off(0); - let mut formatted = String::new(); - - std::mem::swap(&mut formatted, &mut writer.out.buf); - - let start = byte_offset(contents, span.start()) + 1; - let end = byte_offset(contents, span.end()) - 1; + let start = collect_macros::byte_offset(contents, span.start()) + 1; + let end = collect_macros::byte_offset(contents, span.end()) - 1; // Rustfmt will remove the space between the macro and the opening paren if the macro is a single expression - let body_is_solo_expr = body.roots.len() == 1 - && matches!(body.roots[0], BodyNode::RawExpr(_) | BodyNode::Text(_)); + let body_is_solo_expr = body.body.roots.len() == 1 + && matches!(body.body.roots[0], BodyNode::RawExpr(_) | BodyNode::Text(_)); if formatted.len() <= 80 && !formatted.contains('\n') && !body_is_solo_expr { formatted = format!(" {formatted} "); @@ -132,33 +120,20 @@ pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec { formatted_blocks } +/// Write a Callbody (the rsx block) to a string +/// +/// If the tokens can't be formatted, this returns None. This is usually due to an incomplete expression +/// that passed partial expansion but failed to parse. pub fn write_block_out(body: &CallBody) -> Option { let mut buf = Writer::new(""); - - write_body(&mut buf, body); - + buf.write_rsx_call(&body.body).ok()?; buf.consume() } -fn write_body(buf: &mut Writer, body: &CallBody) { - match body.roots.len() { - 0 => {} - 1 if matches!(body.roots[0], BodyNode::Text(_)) => { - write!(buf.out, " ").unwrap(); - buf.write_ident(&body.roots[0]).unwrap(); - write!(buf.out, " ").unwrap(); - } - _ => buf.write_body_indented(&body.roots).unwrap(), - } -} - pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option { let body = CallBody::parse_strict.parse2(expr.mac.tokens).unwrap(); - let mut buf = Writer::new(raw); - - write_body(&mut buf, &body); - + buf.write_rsx_call(&body.body).ok()?; buf.consume() } @@ -166,11 +141,9 @@ pub fn fmt_block(block: &str, indent_level: usize, indent: IndentOptions) -> Opt let body = CallBody::parse_strict.parse_str(block).unwrap(); let mut buf = Writer::new(block); - buf.out.indent = indent; buf.out.indent_level = indent_level; - - write_body(&mut buf, &body); + buf.write_rsx_call(&body.body).ok()?; // writing idents leaves the final line ended at the end of the last ident if buf.out.buf.contains('\n') { @@ -180,16 +153,6 @@ pub fn fmt_block(block: &str, indent_level: usize, indent: IndentOptions) -> Opt buf.consume() } -pub fn apply_format(input: &str, block: FormattedBlock) -> String { - let start = block.start; - let end = block.end; - - let (left, _) = input.split_at(start); - let (_, right) = input.split_at(end); - - format!("{}{}{}", left, block.formatted, right) -} - // Apply all the blocks pub fn apply_formats(input: &str, blocks: Vec) -> String { let mut out = String::new(); @@ -213,24 +176,3 @@ pub fn apply_formats(input: &str, blocks: Vec) -> String { out } - -struct DisplayIfmt<'a>(&'a IfmtInput); - -impl Display for DisplayIfmt<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let inner_tokens = self.0.source.as_ref().unwrap().to_token_stream(); - inner_tokens.fmt(f) - } -} - -pub(crate) fn ifmt_to_string(input: &IfmtInput) -> String { - let mut buf = String::new(); - let display = DisplayIfmt(input); - write!(&mut buf, "{}", display).unwrap(); - buf -} - -pub(crate) fn write_ifmt(input: &IfmtInput, writable: &mut impl Write) -> std::fmt::Result { - let display = DisplayIfmt(input); - write!(writable, "{}", display) -} diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/rsx_block.rs similarity index 59% rename from packages/autofmt/src/element.rs rename to packages/autofmt/src/rsx_block.rs index babda2015..cf03b2c09 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/rsx_block.rs @@ -1,4 +1,4 @@ -use crate::{ifmt_to_string, prettier_please::unparse_expr, Writer}; +use crate::{prettier_please::unparse_expr, Writer}; use dioxus_rsx::*; use proc_macro2::Span; use quote::ToTokens; @@ -30,47 +30,24 @@ enum ShortOptimization { NoOpt, } -/* -// whitespace -div { - // some whitespace - class: "asdasd" - - // whjiot - asdasd // whitespace -} -*/ - impl Writer<'_> { - pub fn write_element(&mut self, el: &Element) -> Result { - let Element { - name, - key, - attributes, - children, - brace, - .. - } = el; - - let brace = brace - .as_ref() - .expect("braces should always be present in strict mode"); - - /* - 1. Write the tag - 2. Write the key - 3. Write the attributes - 4. Write the children - */ - - write!(self.out, "{name} {{")?; - + /// Basically elements and components are the same thing + /// + /// This writes the contents out for both in one function, centralizing the annoying logic like + /// key handling, breaks, closures, etc + pub fn write_rsx_block( + &mut self, + attributes: &[Attribute], + spreads: &[Spread], + children: &[BodyNode], + brace: &Brace, + ) -> Result { // decide if we have any special optimizations // Default with none, opt the cases in one-by-one let mut opt_level = ShortOptimization::NoOpt; // check if we have a lot of attributes - let attr_len = self.is_short_attrs(attributes); + let attr_len = self.is_short_attrs(attributes, spreads); let is_short_attr_list = (attr_len + self.out.indent_level * 4) < 80; let children_len = self.is_short_children(children); let is_small_children = children_len.is_some(); @@ -82,7 +59,7 @@ impl Writer<'_> { // even if the attr is long, it should be put on one line // However if we have childrne we need to just spread them out for readability - if !is_short_attr_list && attributes.len() <= 1 { + if !is_short_attr_list && attributes.len() <= 1 && spreads.is_empty() { if children.is_empty() { opt_level = ShortOptimization::Oneliner; } else { @@ -100,7 +77,7 @@ impl Writer<'_> { } // If there's nothing at all, empty optimization - if attributes.is_empty() && children.is_empty() && key.is_none() { + if attributes.is_empty() && children.is_empty() && spreads.is_empty() { opt_level = ShortOptimization::Empty; // Write comments if they exist @@ -112,49 +89,42 @@ impl Writer<'_> { opt_level = ShortOptimization::NoOpt; } + let has_children = !children.is_empty(); + match opt_level { ShortOptimization::Empty => {} ShortOptimization::Oneliner => { write!(self.out, " ")?; - self.write_attributes(brace, attributes, key, true)?; + self.write_attributes(attributes, spreads, true, brace, has_children)?; - if !children.is_empty() && (!attributes.is_empty() || key.is_some()) { - write!(self.out, ", ")?; + if !children.is_empty() && !attributes.is_empty() { + write!(self.out, " ")?; } - for (id, child) in children.iter().enumerate() { + for child in children.iter() { self.write_ident(child)?; - if id != children.len() - 1 && children.len() > 1 { - write!(self.out, ", ")?; - } } write!(self.out, " ")?; } ShortOptimization::PropsOnTop => { - if !attributes.is_empty() || key.is_some() { + if !attributes.is_empty() { write!(self.out, " ")?; } - self.write_attributes(brace, attributes, key, true)?; - if !children.is_empty() && (!attributes.is_empty() || key.is_some()) { - write!(self.out, ",")?; - } + self.write_attributes(attributes, spreads, true, brace, has_children)?; if !children.is_empty() { self.write_body_indented(children)?; } + self.out.tabbed_line()?; } ShortOptimization::NoOpt => { - self.write_attributes(brace, attributes, key, false)?; - - if !children.is_empty() && (!attributes.is_empty() || key.is_some()) { - write!(self.out, ",")?; - } + self.write_attributes(attributes, spreads, false, brace, has_children)?; if !children.is_empty() { self.write_body_indented(children)?; @@ -164,76 +134,99 @@ impl Writer<'_> { } } - write!(self.out, "}}")?; - Ok(()) } fn write_attributes( &mut self, + attributes: &[Attribute], + spreads: &[Spread], + props_same_line: bool, brace: &Brace, - attributes: &[AttributeType], - key: &Option, - sameline: bool, + has_children: bool, ) -> Result { - let mut attr_iter = attributes.iter().peekable(); - - if let Some(key) = key { - if !sameline { - self.out.indented_tabbed_line()?; - } - write!(self.out, "key: {}", ifmt_to_string(key))?; - if !attributes.is_empty() { - write!(self.out, ",")?; - if sameline { - write!(self.out, " ")?; - } - } + enum AttrType<'a> { + Attr(&'a Attribute), + Spread(&'a Spread), } + let mut attr_iter = attributes + .iter() + .map(AttrType::Attr) + .chain(spreads.iter().map(AttrType::Spread)) + .peekable(); + while let Some(attr) = attr_iter.next() { self.out.indent_level += 1; - if !sameline { - self.write_attr_comments(brace, attr.start())?; + if !props_same_line { + self.write_attr_comments( + brace, + match attr { + AttrType::Attr(attr) => attr.span(), + AttrType::Spread(attr) => attr.expr.span(), + }, + )?; } self.out.indent_level -= 1; - if !sameline { + if !props_same_line { self.out.indented_tabbed_line()?; } - self.write_attribute(attr)?; + match attr { + AttrType::Attr(attr) => self.write_attribute(attr)?, + AttrType::Spread(attr) => self.write_spread_attribute(&attr.expr)?, + } if attr_iter.peek().is_some() { write!(self.out, ",")?; - if sameline { + if props_same_line { write!(self.out, " ")?; } } } - Ok(()) - } + let has_attributes = !attributes.is_empty() || !spreads.is_empty(); - fn write_attribute_name(&mut self, attr: &ElementAttrName) -> Result { - match attr { - ElementAttrName::BuiltIn(name) => { - write!(self.out, "{}", name)?; - } - ElementAttrName::Custom(name) => { - write!(self.out, "{}", name.to_token_stream())?; - } + if has_attributes && has_children { + write!(self.out, ",")?; } Ok(()) } - fn write_attribute_value(&mut self, value: &ElementAttrValue) -> Result { + fn write_attribute(&mut self, attr: &Attribute) -> Result { + self.write_attribute_name(&attr.name)?; + + // if the attribute is a shorthand, we don't need to write the colon, just the name + if !attr.can_be_shorthand() { + write!(self.out, ": ")?; + self.write_attribute_value(&attr.value)?; + } + + Ok(()) + } + + fn write_attribute_name(&mut self, attr: &AttributeName) -> Result { + match attr { + AttributeName::BuiltIn(name) => { + write!(self.out, "{}", name)?; + } + AttributeName::Custom(name) => { + write!(self.out, "{}", name.to_token_stream())?; + } + AttributeName::Spread(_) => unreachable!(), + } + + Ok(()) + } + + fn write_attribute_value(&mut self, value: &AttributeValue) -> Result { match value { - ElementAttrValue::AttrOptionalExpr { condition, value } => { + AttributeValue::AttrOptionalExpr { condition, value } => { write!( self.out, "if {condition} {{ ", @@ -242,56 +235,47 @@ impl Writer<'_> { self.write_attribute_value(value)?; write!(self.out, " }}")?; } - ElementAttrValue::AttrLiteral(value) => { - write!(self.out, "{value}", value = ifmt_to_string(value))?; + AttributeValue::AttrLiteral(value) => { + write!(self.out, "{value}")?; } - ElementAttrValue::Shorthand(value) => { - write!(self.out, "{value}",)?; + AttributeValue::Shorthand(value) => { + write!(self.out, "{value}")?; + } + AttributeValue::EventTokens(closure) => { + self.write_partial_closure(closure)?; } - ElementAttrValue::AttrExpr(value) => { - let out = self.unparse_expr(value); - let mut lines = out.split('\n').peekable(); - let first = lines.next().unwrap(); - // a one-liner for whatever reason - // Does not need a new line + AttributeValue::AttrExpr(value) => { + let Ok(expr) = value.as_expr() else { + return Err(fmt::Error); + }; + + let pretty_expr = self.retrieve_formatted_expr(&expr).to_string(); + self.write_mulitiline_tokens(pretty_expr)?; + } + } + + Ok(()) + } + + fn write_mulitiline_tokens(&mut self, out: String) -> Result { + let mut lines = out.split('\n').peekable(); + let first = lines.next().unwrap(); + + // a one-liner for whatever reason + // Does not need a new line + if lines.peek().is_none() { + write!(self.out, "{first}")?; + } else { + writeln!(self.out, "{first}")?; + + while let Some(line) = lines.next() { + self.out.indented_tab()?; + write!(self.out, "{line}")?; if lines.peek().is_none() { - write!(self.out, "{first}")?; + write!(self.out, "")?; } else { - writeln!(self.out, "{first}")?; - - while let Some(line) = lines.next() { - self.out.indented_tab()?; - write!(self.out, "{line}")?; - if lines.peek().is_none() { - write!(self.out, "")?; - } else { - writeln!(self.out)?; - } - } - } - } - ElementAttrValue::EventTokens(tokens) => { - let out = self.retrieve_formatted_expr(tokens).to_string(); - let mut lines = out.split('\n').peekable(); - let first = lines.next().unwrap(); - - // a one-liner for whatever reason - // Does not need a new line - if lines.peek().is_none() { - write!(self.out, "{first}")?; - } else { - writeln!(self.out, "{first}")?; - - while let Some(line) = lines.next() { - self.out.indented_tab()?; - write!(self.out, "{line}")?; - if lines.peek().is_none() { - write!(self.out, "")?; - } else { - writeln!(self.out)?; - } - } + writeln!(self.out)?; } } } @@ -299,28 +283,34 @@ impl Writer<'_> { Ok(()) } - fn write_attribute(&mut self, attr: &AttributeType) -> Result { - match attr { - AttributeType::Named(attr) => self.write_named_attribute(attr), - AttributeType::Spread(attr) => self.write_spread_attribute(attr), - } - } - - fn write_named_attribute(&mut self, attr: &ElementAttrNamed) -> Result { - self.write_attribute_name(&attr.attr.name)?; - - // if the attribute is a shorthand, we don't need to write the colon, just the name - if !attr.attr.can_be_shorthand() { - write!(self.out, ": ")?; - self.write_attribute_value(&attr.attr.value)?; + /// Write out the special PartialClosure type from the rsx crate + /// Basically just write token by token until we hit the block and then try and format *that* + /// We can't just ToTokens + fn write_partial_closure(&mut self, closure: &PartialClosure) -> Result { + // Write the pretty version of the closure + if let Ok(expr) = closure.as_expr() { + let pretty_expr = self.retrieve_formatted_expr(&expr).to_string(); + self.write_mulitiline_tokens(pretty_expr)?; + return Ok(()); } - Ok(()) + // If we can't parse the closure, writing it is also a failure + // rustfmt won't be able to parse it either so no point in trying + Err(fmt::Error) } fn write_spread_attribute(&mut self, attr: &Expr) -> Result { - write!(self.out, "..")?; - write!(self.out, "{}", unparse_expr(attr))?; + let formatted = unparse_expr(attr); + + let mut lines = formatted.lines(); + + let first_line = lines.next().unwrap(); + + write!(self.out, "..{first_line}")?; + for line in lines { + self.out.indented_tabbed_line()?; + write!(self.out, "{line}")?; + } Ok(()) } @@ -363,10 +353,10 @@ impl Writer<'_> { } match children { - [BodyNode::Text(ref text)] => Some(ifmt_to_string(text).len()), + [BodyNode::Text(ref text)] => Some(text.input.to_string_with_quotes().len()), // TODO: let rawexprs to be inlined - [BodyNode::RawExpr(ref expr)] => get_expr_length(expr), + [BodyNode::RawExpr(ref expr)] => Some(get_expr_length(expr.span())), // TODO: let rawexprs to be inlined [BodyNode::Component(ref comp)] if comp.fields.is_empty() => Some( @@ -430,12 +420,11 @@ impl Writer<'_> { } } -fn get_expr_length(expr: &impl Spanned) -> Option { - let span = expr.span(); +fn get_expr_length(span: Span) -> usize { let (start, end) = (span.start(), span.end()); if start.line == end.line { - Some(end.column - start.column) + end.column - start.column } else { - None + 10000 } } diff --git a/packages/autofmt/src/writer.rs b/packages/autofmt/src/writer.rs index 7c840c763..afd31df36 100644 --- a/packages/autofmt/src/writer.rs +++ b/packages/autofmt/src/writer.rs @@ -1,4 +1,9 @@ -use dioxus_rsx::{AttributeType, BodyNode, ElementAttrValue, ForLoop, IfChain, IfmtInput}; +use crate::buffer::Buffer; +use crate::collect_macros::byte_offset; +use dioxus_rsx::{ + Attribute as AttributeType, AttributeName, AttributeValue as ElementAttrValue, BodyNode, + Component, Element, ForLoop, IfChain, Spread, TemplateBody, +}; use proc_macro2::{LineColumn, Span}; use quote::ToTokens; use std::{ @@ -7,32 +12,15 @@ use std::{ }; use syn::{spanned::Spanned, token::Brace, Expr}; -use crate::buffer::Buffer; -use crate::ifmt_to_string; - #[derive(Debug)] pub struct Writer<'a> { pub raw_src: &'a str, pub src: Vec<&'a str>, - pub cached_formats: HashMap, + pub cached_formats: HashMap, pub comments: VecDeque, pub out: Buffer, } -#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)] -pub struct Location { - pub line: usize, - pub col: usize, -} -impl Location { - pub fn new(start: LineColumn) -> Self { - Self { - line: start.line, - col: start.column, - } - } -} - impl<'a> Writer<'a> { pub fn new(raw_src: &'a str) -> Self { let src = raw_src.lines().collect(); @@ -45,20 +33,143 @@ impl<'a> Writer<'a> { } } + pub fn consume(self) -> Option { + Some(self.out.buf) + } + + pub fn write_rsx_call(&mut self, body: &TemplateBody) -> Result { + match body.roots.len() { + 0 => {} + 1 if matches!(body.roots[0], BodyNode::Text(_)) => { + write!(self.out, " ")?; + self.write_ident(&body.roots[0])?; + write!(self.out, " ")?; + } + _ => self.write_body_indented(&body.roots)?, + } + + Ok(()) + } + // Expects to be written directly into place pub fn write_ident(&mut self, node: &BodyNode) -> Result { match node { BodyNode::Element(el) => self.write_element(el), BodyNode::Component(component) => self.write_component(component), - BodyNode::Text(text) => self.out.write_text(text), + BodyNode::Text(text) => self.out.write_text(&text.input), BodyNode::RawExpr(exp) => self.write_raw_expr(exp.span()), BodyNode::ForLoop(forloop) => self.write_for_loop(forloop), BodyNode::IfChain(ifchain) => self.write_if_chain(ifchain), } } - pub fn consume(self) -> Option { - Some(self.out.buf) + pub fn write_element(&mut self, el: &Element) -> Result { + let Element { + name, + raw_attributes: attributes, + children, + spreads, + brace, + .. + } = el; + + /* + 1. Write the tag + 2. Write the key + 3. Write the attributes + 4. Write the children + */ + + write!(self.out, "{name} {{")?; + + self.write_rsx_block(attributes, spreads, children, brace)?; + + write!(self.out, "}}")?; + + Ok(()) + } + + pub fn write_component( + &mut self, + Component { + name, + fields, + children, + generics, + spreads, + brace, + .. + }: &Component, + ) -> Result { + // Write the path by to_tokensing it and then removing all whitespace + let mut name = name.to_token_stream().to_string(); + name.retain(|c| !c.is_whitespace()); + write!(self.out, "{name}")?; + + // Same idea with generics, write those via the to_tokens method and then remove all whitespace + if let Some(generics) = generics { + let mut written = generics.to_token_stream().to_string(); + written.retain(|c| !c.is_whitespace()); + write!(self.out, "{written}")?; + } + + write!(self.out, " {{")?; + + self.write_rsx_block(fields, spreads, &children.roots, brace)?; + + write!(self.out, "}}")?; + + Ok(()) + } + + pub fn write_raw_expr(&mut self, placement: Span) -> Result { + /* + We want to normalize the expr to the appropriate indent level. + */ + + let start = placement.start(); + let end = placement.end(); + + // if the expr is on one line, just write it directly + if start.line == end.line { + // split counting utf8 chars + let start = byte_offset(self.raw_src, start); + let end = byte_offset(self.raw_src, end); + let row = self.raw_src[start..end].trim(); + write!(self.out, "{row}")?; + return Ok(()); + } + + // If the expr is multiline, we want to collect all of its lines together and write them out properly + // This involves unshifting the first line if it's aligned + let first_line = &self.src[start.line - 1]; + write!(self.out, "{}", &first_line[start.column..].trim_start())?; + + let prev_block_indent_level = self.out.indent.count_indents(first_line); + + for (id, line) in self.src[start.line..end.line].iter().enumerate() { + writeln!(self.out)?; + + // check if this is the last line + let line = { + if id == (end.line - start.line) - 1 { + &line[..end.column] + } else { + line + } + }; + + // trim the leading whitespace + let previous_indent = self.out.indent.count_indents(line); + let offset = previous_indent.saturating_sub(prev_block_indent_level); + let required_indent = self.out.indent_level + offset; + self.out.write_tabs(required_indent)?; + + let line = line.trim_start(); + write!(self.out, "{line}")?; + } + + Ok(()) } pub fn write_attr_comments(&mut self, brace: &Brace, attr_span: Span) -> Result { @@ -123,28 +234,13 @@ impl<'a> Writer<'a> { } pub fn write_body_no_indent(&mut self, children: &[BodyNode]) -> Result { - let last_child = children.len(); - let iter = children.iter().peekable().enumerate(); - - for (idx, child) in iter { + for child in children { if self.current_span_is_primary(child.span()) { self.write_comments(child.span())?; - } + }; - match child { - // check if the expr is a short - BodyNode::RawExpr { .. } => { - self.out.tabbed_line()?; - self.write_ident(child)?; - if idx != last_child - 1 { - write!(self.out, ",")?; - } - } - _ => { - self.out.tabbed_line()?; - self.write_ident(child)?; - } - } + self.out.tabbed_line()?; + self.write_ident(child)?; } Ok(()) @@ -155,31 +251,35 @@ impl<'a> Writer<'a> { ElementAttrValue::AttrOptionalExpr { condition, value } => { let condition_len = self.retrieve_formatted_expr(condition).len(); let value_len = self.attr_value_len(value); - condition_len + value_len + 6 } - ElementAttrValue::AttrLiteral(lit) => ifmt_to_string(lit).len(), + ElementAttrValue::AttrLiteral(lit) => lit.to_string().len(), ElementAttrValue::Shorthand(expr) => expr.span().line_length(), - ElementAttrValue::AttrExpr(expr) => { - let out = self.retrieve_formatted_expr(expr); - if out.contains('\n') { - 100000 - } else { - out.len() - } - } - ElementAttrValue::EventTokens(tokens) => { - let as_str = self.retrieve_formatted_expr(tokens); - if as_str.contains('\n') { - 100000 - } else { - as_str.len() - } - } + ElementAttrValue::AttrExpr(expr) => expr + .as_expr() + .map(|expr| self.attr_expr_len(&expr)) + .unwrap_or(100000), + ElementAttrValue::EventTokens(closure) => closure + .as_expr() + .map(|expr| self.attr_expr_len(&expr)) + .unwrap_or(100000), } } - pub(crate) fn is_short_attrs(&mut self, attributes: &[AttributeType]) -> usize { + fn attr_expr_len(&mut self, expr: &Expr) -> usize { + let out = self.retrieve_formatted_expr(expr); + if out.contains('\n') { + 100000 + } else { + out.len() + } + } + + pub(crate) fn is_short_attrs( + &mut self, + attributes: &[AttributeType], + spreads: &[Spread], + ) -> usize { let mut total = 0; // No more than 3 attributes before breaking the line @@ -188,8 +288,8 @@ impl<'a> Writer<'a> { } for attr in attributes { - if self.current_span_is_primary(attr.start()) { - 'line: for line in self.src[..attr.start().start().line - 1].iter().rev() { + if self.current_span_is_primary(attr.span()) { + 'line: for line in self.src[..attr.span().start().line - 1].iter().rev() { match (line.trim().starts_with("//"), line.is_empty()) { (true, _) => return 100000, (_, true) => continue 'line, @@ -198,39 +298,37 @@ impl<'a> Writer<'a> { } } - match attr { - AttributeType::Named(attr) => { - let name_len = match &attr.attr.name { - dioxus_rsx::ElementAttrName::BuiltIn(name) => { - let name = name.to_string(); - name.len() - } - dioxus_rsx::ElementAttrName::Custom(name) => name.value().len() + 2, - }; - total += name_len; - - // - if attr.attr.value.is_shorthand() { - total += 2; - } else { - total += self.attr_value_len(&attr.attr.value); - } - } - AttributeType::Spread(expr) => { - let expr_len = self.retrieve_formatted_expr(expr).len(); - total += expr_len + 3; + let name_len = match &attr.name { + AttributeName::BuiltIn(name) => { + let name = name.to_string(); + name.len() } + AttributeName::Custom(name) => name.value().len() + 2, + AttributeName::Spread(_) => unreachable!(), }; + total += name_len; + + // + if attr.can_be_shorthand() { + total += 2; + } else { + total += self.attr_value_len(&attr.value); + } total += 6; } + for spread in spreads { + let expr_len = self.retrieve_formatted_expr(&spread.expr).len(); + total += expr_len + 3; + } + total } #[allow(clippy::map_entry)] pub fn retrieve_formatted_expr(&mut self, expr: &Expr) -> &str { - let loc = Location::new(expr.span().start()); + let loc = expr.span().start(); if !self.cached_formats.contains_key(&loc) { let formatted = self.unparse_expr(expr); @@ -254,7 +352,7 @@ impl<'a> Writer<'a> { return Ok(()); } - self.write_body_indented(&forloop.body)?; + self.write_body_indented(&forloop.body.roots)?; self.out.tabbed_line()?; write!(self.out, "}}")?; @@ -280,7 +378,7 @@ impl<'a> Writer<'a> { self.write_inline_expr(cond)?; - self.write_body_indented(then_branch)?; + self.write_body_indented(&then_branch.roots)?; if let Some(else_if_branch) = else_if_branch { // write the closing bracket and else @@ -292,7 +390,7 @@ impl<'a> Writer<'a> { self.out.tabbed_line()?; write!(self.out, "}} else {{")?; - self.write_body_indented(else_branch)?; + self.write_body_indented(&else_branch.roots)?; branch = None; } else { branch = None; @@ -329,13 +427,6 @@ impl<'a> Writer<'a> { Ok(()) } - - pub(crate) fn key_len(&self, key: Option<&IfmtInput>) -> usize { - match key { - Some(key) => ifmt_to_string(key).len() + 5, - None => 0, - } - } } pub(crate) trait SpanLength { diff --git a/packages/autofmt/tests/samples/attributes.rsx b/packages/autofmt/tests/samples/attributes.rsx index f9f749e3d..8a8718299 100644 --- a/packages/autofmt/tests/samples/attributes.rsx +++ b/packages/autofmt/tests/samples/attributes.rsx @@ -16,8 +16,8 @@ rsx! { div { "hi" } h2 { class: "asd" } } + Component:: {} Component {} - Component {} } // Long attributes diff --git a/packages/autofmt/tests/samples/commentshard.rsx b/packages/autofmt/tests/samples/commentshard.rsx index 0a05cfe4a..5db28b27c 100644 --- a/packages/autofmt/tests/samples/commentshard.rsx +++ b/packages/autofmt/tests/samples/commentshard.rsx @@ -8,17 +8,17 @@ rsx! { "hello world" // Comments - {expr1}, + {expr1} // Comments - {expr2}, + {expr2} // Comments // Comments // Comments // Comments // Comments - {expr3}, + {expr3} div { // todo some work in here diff --git a/packages/autofmt/tests/samples/docsite.rsx b/packages/autofmt/tests/samples/docsite.rsx index e6943c9b9..d244d7514 100644 --- a/packages/autofmt/tests/samples/docsite.rsx +++ b/packages/autofmt/tests/samples/docsite.rsx @@ -12,7 +12,11 @@ pub(crate) fn Nav() -> Element { let mut sidebar = SHOW_SIDEBAR.write(); *sidebar = !*sidebar; }, - MaterialIcon { name: "menu", size: 24, color: MaterialIconColor::Dark } + MaterialIcon { + name: "menu", + size: 24, + color: MaterialIconColor::Dark + } } div { class: "flex z-50 md:flex-1 px-2", LinkList {} } diff --git a/packages/autofmt/tests/samples/long.rsx b/packages/autofmt/tests/samples/long.rsx index 4b283475a..bd0386fb8 100644 --- a/packages/autofmt/tests/samples/long.rsx +++ b/packages/autofmt/tests/samples/long.rsx @@ -34,7 +34,7 @@ pub fn Explainer<'a>( rsx! { div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", - {left}, + {left} {right} } } diff --git a/packages/autofmt/tests/samples/misplaced.rsx b/packages/autofmt/tests/samples/misplaced.rsx index 22aac3e15..159afdf31 100644 --- a/packages/autofmt/tests/samples/misplaced.rsx +++ b/packages/autofmt/tests/samples/misplaced.rsx @@ -12,7 +12,11 @@ pub(crate) fn Nav() -> Element { let mut sidebar = SHOW_SIDEBAR.write(); *sidebar = !*sidebar; }, - MaterialIcon { name: "menu", size: 24, color: MaterialIconColor::Dark } + MaterialIcon { + name: "menu", + size: 24, + color: MaterialIconColor::Dark + } } div { class: "flex z-50 md:flex-1 px-2", LinkList {} } } diff --git a/packages/autofmt/tests/wrong/multiexpr-4sp.rsx b/packages/autofmt/tests/wrong/multiexpr-4sp.rsx index cbf84a125..d38a5befc 100644 --- a/packages/autofmt/tests/wrong/multiexpr-4sp.rsx +++ b/packages/autofmt/tests/wrong/multiexpr-4sp.rsx @@ -1,7 +1,7 @@ fn ItWroks() { rsx! { div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", - {left}, + {left} {right} } } diff --git a/packages/autofmt/tests/wrong/multiexpr-tab.rsx b/packages/autofmt/tests/wrong/multiexpr-tab.rsx index faf6d717d..cb1508381 100644 --- a/packages/autofmt/tests/wrong/multiexpr-tab.rsx +++ b/packages/autofmt/tests/wrong/multiexpr-tab.rsx @@ -1,7 +1,7 @@ fn ItWroks() { rsx! { div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", - {left}, + {left} {right} } } diff --git a/packages/cli-config/Cargo.toml b/packages/cli-config/Cargo.toml index 1ce7edc86..97666b088 100644 --- a/packages/cli-config/Cargo.toml +++ b/packages/cli-config/Cargo.toml @@ -13,20 +13,21 @@ clap = { version = "4.2", features = ["derive"], optional = true } serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" toml = { workspace = true, optional = true } -cargo_toml = { version = "0.18.0", optional = true } +cargo_toml = { workspace = true, optional = true } once_cell = "1.18.0" tracing = { workspace = true } # bundling -tauri-bundler = { version = "=1.4.0", features = ["native-tls-vendored"], optional = true } -tauri-utils = { version = "=1.5.*", optional = true } +tauri-bundler = { workspace = true, optional = true } +tauri-utils = { workspace = true, optional = true } dirs = { workspace = true, optional = true } [features] default = ["read-config"] -cli = ["dep:tauri-bundler", "dep:tauri-utils", "dep:clap", "dep:toml", "dep:cargo_toml", "dep:dirs"] +cli = ["dep:tauri-bundler", "dep:tauri-utils", "read-from-args", "dep:toml", "dep:cargo_toml", "dep:dirs"] read-config = [] +read-from-args = ["dep:clap"] [package.metadata.docs.rs] cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] diff --git a/packages/cli-config/src/cargo.rs b/packages/cli-config/src/cargo.rs deleted file mode 100644 index 97e4040db..000000000 --- a/packages/cli-config/src/cargo.rs +++ /dev/null @@ -1,115 +0,0 @@ -//! Utilities for working with cargo and rust files -use std::error::Error; -use std::{ - env, - fmt::{Display, Formatter}, - fs, - path::{Path, PathBuf}, - process::Command, - str, -}; - -#[derive(Debug, Clone)] -pub struct CargoError { - msg: String, -} - -impl CargoError { - pub fn new(msg: String) -> Self { - Self { msg } - } -} - -impl Display for CargoError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "CargoError: {}", self.msg) - } -} - -impl Error for CargoError {} - -/// How many parent folders are searched for a `Cargo.toml` -const MAX_ANCESTORS: u32 = 10; - -/// Some fields parsed from `cargo metadata` command -pub struct Metadata { - pub workspace_root: PathBuf, - pub target_directory: PathBuf, -} - -/// Returns the root of the crate that the command is run from -/// -/// If the command is run from the workspace root, this will return the top-level Cargo.toml -pub fn crate_root() -> Result { - // From the current directory we work our way up, looking for `Cargo.toml` - env::current_dir() - .ok() - .and_then(|mut wd| { - for _ in 0..MAX_ANCESTORS { - if contains_manifest(&wd) { - return Some(wd); - } - if !wd.pop() { - break; - } - } - None - }) - .ok_or_else(|| { - CargoError::new("Failed to find directory containing Cargo.toml".to_string()) - }) -} - -/// Checks if the directory contains `Cargo.toml` -fn contains_manifest(path: &Path) -> bool { - fs::read_dir(path) - .map(|entries| { - entries - .filter_map(Result::ok) - .any(|ent| &ent.file_name() == "Cargo.toml") - }) - .unwrap_or(false) -} - -impl Metadata { - /// Returns the struct filled from `cargo metadata` output - /// TODO @Jon, find a different way that doesn't rely on the cargo metadata command (it's slow) - pub fn get() -> Result { - let output = Command::new("cargo") - .args(["metadata"]) - .output() - .map_err(|_| CargoError::new("Manifset".to_string()))?; - - if !output.status.success() { - let mut msg = str::from_utf8(&output.stderr).unwrap().trim(); - if msg.starts_with("error: ") { - msg = &msg[7..]; - } - - return Err(CargoError::new(msg.to_string())); - } - - let stdout = str::from_utf8(&output.stdout).unwrap(); - if let Some(line) = stdout.lines().next() { - let meta: serde_json::Value = serde_json::from_str(line) - .map_err(|_| CargoError::new("InvalidOutput".to_string()))?; - - let workspace_root = meta["workspace_root"] - .as_str() - .ok_or_else(|| CargoError::new("InvalidOutput".to_string()))? - .into(); - - let target_directory = meta["target_directory"] - .as_str() - .ok_or_else(|| CargoError::new("InvalidOutput".to_string()))? - .into(); - - return Ok(Self { - workspace_root, - target_directory, - }); - } - - Err(CargoError::new("InvalidOutput".to_string())) - } -} diff --git a/packages/cli-config/src/config.rs b/packages/cli-config/src/config.rs index 6ed1e669b..6e3f2a57c 100644 --- a/packages/cli-config/src/config.rs +++ b/packages/cli-config/src/config.rs @@ -1,16 +1,19 @@ use crate::BundleConfig; -use crate::CargoError; -use core::fmt::{Display, Formatter}; use serde::{Deserialize, Serialize}; +use std::fmt::Display; use std::path::PathBuf; +use std::str::FromStr; -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)] +#[derive( + Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug, Default, +)] #[cfg_attr(feature = "cli", derive(clap::ValueEnum))] #[non_exhaustive] pub enum Platform { /// Targeting the web platform using WASM #[cfg_attr(feature = "cli", clap(name = "web"))] #[serde(rename = "web")] + #[default] Web, /// Targeting the desktop platform using Tao/Wry-based webview @@ -29,6 +32,56 @@ pub enum Platform { StaticGeneration, } +/// An error that occurs when a platform is not recognized +pub struct UnknownPlatformError; + +impl std::fmt::Display for UnknownPlatformError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Unknown platform") + } +} + +impl FromStr for Platform { + type Err = UnknownPlatformError; + + fn from_str(s: &str) -> Result { + match s { + "web" => Ok(Self::Web), + "desktop" => Ok(Self::Desktop), + "fullstack" => Ok(Self::Fullstack), + "static-generation" => Ok(Self::StaticGeneration), + _ => Err(UnknownPlatformError), + } + } +} + +impl Display for Platform { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let feature = self.feature_name(); + f.write_str(feature) + } +} + +impl Platform { + /// All platforms the dioxus CLI supports + pub const ALL: &'static [Self] = &[ + Platform::Web, + Platform::Desktop, + Platform::Fullstack, + Platform::StaticGeneration, + ]; + + /// Get the feature name for the platform in the dioxus crate + pub fn feature_name(&self) -> &str { + match self { + Platform::Web => "web", + Platform::Desktop => "desktop", + Platform::Fullstack => "fullstack", + Platform::StaticGeneration => "static-generation", + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DioxusConfig { pub application: ApplicationConfig, @@ -37,165 +90,6 @@ pub struct DioxusConfig { #[serde(default)] pub bundle: BundleConfig, - - #[cfg(feature = "cli")] - #[serde(default = "default_plugin")] - pub plugin: toml::Value, - - #[cfg(feature = "cli")] - pub cli_settings: Option, -} - -#[cfg(feature = "cli")] -fn default_plugin() -> toml::Value { - toml::Value::Boolean(true) -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LoadDioxusConfigError { - location: String, - error: String, -} - -impl std::fmt::Display for LoadDioxusConfigError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{} {}", self.location, self.error) - } -} - -impl std::error::Error for LoadDioxusConfigError {} - -#[derive(Debug)] -#[non_exhaustive] -pub enum CrateConfigError { - Cargo(CargoError), - Io(std::io::Error), - #[cfg(feature = "cli")] - Toml(toml::de::Error), - LoadDioxusConfig(LoadDioxusConfigError), -} - -impl From for CrateConfigError { - fn from(err: CargoError) -> Self { - Self::Cargo(err) - } -} - -impl From for CrateConfigError { - fn from(err: std::io::Error) -> Self { - Self::Io(err) - } -} - -#[cfg(feature = "cli")] -impl From for CrateConfigError { - fn from(err: toml::de::Error) -> Self { - Self::Toml(err) - } -} - -impl From for CrateConfigError { - fn from(err: LoadDioxusConfigError) -> Self { - Self::LoadDioxusConfig(err) - } -} - -impl Display for CrateConfigError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Self::Cargo(err) => write!(f, "{}", err), - Self::Io(err) => write!(f, "{}", err), - #[cfg(feature = "cli")] - Self::Toml(err) => write!(f, "{}", err), - Self::LoadDioxusConfig(err) => write!(f, "{}", err), - } - } -} - -impl std::error::Error for CrateConfigError {} - -impl DioxusConfig { - #[cfg(feature = "cli")] - /// Load the dioxus config from a path - #[tracing::instrument] - pub fn load(bin: Option) -> Result, CrateConfigError> { - use crate::CliSettings; - - let crate_dir = crate::cargo::crate_root(); - - let crate_dir = match crate_dir { - Ok(dir) => { - if let Some(bin) = bin { - dir.join(bin) - } else { - dir - } - } - Err(_) => return Ok(None), - }; - let crate_dir = crate_dir.as_path(); - - let Some(dioxus_conf_file) = acquire_dioxus_toml(crate_dir) else { - tracing::warn!(?crate_dir, "no dioxus config found for"); - return Ok(None); - }; - - let dioxus_conf_file = dioxus_conf_file.as_path(); - let cfg = toml::from_str::(&std::fs::read_to_string(dioxus_conf_file)?) - .map_err(|err| { - let error_location = dioxus_conf_file - .strip_prefix(crate_dir) - .unwrap_or(dioxus_conf_file) - .display(); - CrateConfigError::LoadDioxusConfig(LoadDioxusConfigError { - location: error_location.to_string(), - error: err.to_string(), - }) - }) - .map(Some); - match cfg { - Ok(Some(mut cfg)) => { - let name = cfg.application.name.clone(); - if cfg.bundle.identifier.is_none() { - cfg.bundle.identifier = Some(format!("io.github.{name}")); - } - if cfg.bundle.publisher.is_none() { - cfg.bundle.publisher = Some(name); - } - - // Handle Cli Settings - if cfg.cli_settings.is_none() { - cfg.cli_settings = Some(CliSettings::default()); - } - - let cli_settings = cfg.cli_settings.as_mut().unwrap(); - // If the project-level settings doesn't exist, let's grab it from global. - if let Some(global_cli_settings) = crate::CliSettings::from_global() { - if cli_settings.always_hot_reload.is_none() { - cli_settings.always_hot_reload = global_cli_settings.always_hot_reload; - } - if cli_settings.always_open_browser.is_none() { - cli_settings.always_open_browser = global_cli_settings.always_open_browser; - } - } - - Ok(Some(cfg)) - } - cfg => cfg, - } - } -} - -#[cfg(feature = "cli")] -#[tracing::instrument] -fn acquire_dioxus_toml(dir: &std::path::Path) -> Option { - use tracing::trace; - - ["Dioxus.toml", "dioxus.toml"] - .into_iter() - .map(|file| dir.join(file)) - .inspect(|path| trace!("checking [{path:?}]")) - .find(|path| path.is_file()) } impl Default for DioxusConfig { @@ -208,9 +102,6 @@ impl Default for DioxusConfig { out_dir: out_dir_default(), asset_dir: asset_dir_default(), - #[cfg(feature = "cli")] - tools: Default::default(), - sub_package: None, }, web: WebConfig { @@ -242,11 +133,6 @@ impl Default for DioxusConfig { publisher: Some(name), ..Default::default() }, - #[cfg(feature = "cli")] - plugin: toml::Value::Table(toml::map::Map::new()), - - #[cfg(feature = "cli")] - cli_settings: Some(crate::CliSettings::default()), } } } @@ -265,10 +151,6 @@ pub struct ApplicationConfig { #[serde(default = "asset_dir_default")] pub asset_dir: PathBuf, - #[cfg(feature = "cli")] - #[serde(default)] - pub tools: std::collections::HashMap, - #[serde(default)] pub sub_package: Option, } @@ -362,6 +244,16 @@ pub struct WebAppConfig { pub base_path: Option, } +impl WebAppConfig { + /// Get the normalized base path for the application with `/` trimmed from both ends. If the base path is not set, this will return `.`. + pub fn base_path(&self) -> &str { + match &self.base_path { + Some(path) => path.trim_matches('/'), + None => ".", + } + } +} + impl Default for WebAppConfig { fn default() -> Self { Self { @@ -429,228 +321,6 @@ pub struct WebHttpsConfig { pub cert_path: Option, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CrateConfig { - pub crate_dir: PathBuf, - pub workspace_dir: PathBuf, - pub target_dir: PathBuf, - #[cfg(feature = "cli")] - pub manifest: cargo_toml::Manifest, - pub executable: ExecutableType, - pub dioxus_config: DioxusConfig, - pub release: bool, - pub hot_reload: bool, - pub cross_origin_policy: bool, - pub verbose: bool, - pub custom_profile: Option, - pub features: Option>, - pub target: Option, - pub cargo_args: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ExecutableType { - Binary(String), - Lib(String), - Example(String), -} - -impl ExecutableType { - /// Get the name of the executable if it is a binary or an example. - pub fn executable(&self) -> Option<&str> { - match self { - Self::Binary(bin) | Self::Example(bin) => Some(bin), - _ => None, - } - } -} - -impl CrateConfig { - #[cfg(feature = "cli")] - pub fn new(bin: Option) -> Result { - let dioxus_config = DioxusConfig::load(bin.clone())?.unwrap_or_default(); - - let crate_root = crate::crate_root()?; - - let crate_dir = if let Some(package) = &dioxus_config.application.sub_package { - crate_root.join(package) - } else if let Some(bin) = bin { - crate_root.join(bin) - } else { - crate_root - }; - - let meta = crate::Metadata::get()?; - let workspace_dir = meta.workspace_root; - let target_dir = meta.target_directory; - - let cargo_def = &crate_dir.join("Cargo.toml"); - - let manifest = cargo_toml::Manifest::from_path(cargo_def).unwrap(); - - let mut output_filename = String::from("dioxus_app"); - if let Some(package) = &manifest.package.as_ref() { - output_filename = match &package.default_run { - Some(default_run_target) => default_run_target.to_owned(), - None => manifest - .bin - .iter() - .find(|b| { - #[allow(clippy::useless_asref)] - let matching_bin = - b.name == manifest.package.as_ref().map(|pkg| pkg.name.clone()); - matching_bin - }) - .or(manifest - .bin - .iter() - .find(|b| b.path == Some("src/main.rs".to_owned()))) - .or(manifest.bin.first()) - .or(manifest.lib.as_ref()) - .and_then(|prod| prod.name.clone()) - .unwrap_or(String::from("dioxus_app")), - }; - } - - let executable = ExecutableType::Binary(output_filename); - - let release = false; - let hot_reload = false; - let cross_origin_policy = false; - let verbose = false; - let custom_profile = None; - let features = None; - let target = None; - let cargo_args = vec![]; - - Ok(Self { - crate_dir, - workspace_dir, - target_dir, - #[cfg(feature = "cli")] - manifest, - executable, - dioxus_config, - release, - hot_reload, - cross_origin_policy, - verbose, - custom_profile, - features, - target, - cargo_args, - }) - } - - /// Compose an asset directory. Represents the typical "public" directory - /// with publicly available resources (configurable in the `Dioxus.toml`). - pub fn asset_dir(&self) -> PathBuf { - self.crate_dir - .join(&self.dioxus_config.application.asset_dir) - } - - /// Compose an out directory. Represents the typical "dist" directory that - /// is "distributed" after building an application (configurable in the - /// `Dioxus.toml`). - pub fn out_dir(&self) -> PathBuf { - self.crate_dir.join(&self.dioxus_config.application.out_dir) - } - - /// Compose an out directory for the fullstack platform. See `out_dir()` - /// method. - pub fn fullstack_out_dir(&self) -> PathBuf { - self.crate_dir.join(".dioxus") - } - - /// Compose a target directory for the server (fullstack-only?). - pub fn server_target_dir(&self) -> PathBuf { - self.fullstack_out_dir().join("ssr") - } - - /// Compose a target directory for the client (fullstack-only?). - pub fn client_target_dir(&self) -> PathBuf { - self.fullstack_out_dir().join("web") - } - - pub fn as_example(&mut self, example_name: String) -> &mut Self { - self.executable = ExecutableType::Example(example_name); - self - } - - pub fn with_release(&mut self, release: bool) -> &mut Self { - self.release = release; - self - } - - pub fn with_hot_reload(&mut self, hot_reload: bool) -> &mut Self { - self.hot_reload = hot_reload; - self - } - - pub fn with_cross_origin_policy(&mut self, cross_origin_policy: bool) -> &mut Self { - self.cross_origin_policy = cross_origin_policy; - self - } - - pub fn with_verbose(&mut self, verbose: bool) -> &mut Self { - self.verbose = verbose; - self - } - - pub fn set_profile(&mut self, profile: String) -> &mut Self { - self.custom_profile = Some(profile); - self - } - - pub fn set_features(&mut self, features: Vec) -> &mut Self { - self.features = Some(features); - self - } - - pub fn set_target(&mut self, target: String) -> &mut Self { - self.target = Some(target); - self - } - - pub fn set_cargo_args(&mut self, cargo_args: Vec) -> &mut Self { - self.cargo_args = cargo_args; - self - } - - pub fn add_features(&mut self, feature: Vec) -> &mut Self { - if let Some(features) = &mut self.features { - features.extend(feature); - } else { - self.features = Some(feature); - } - self - } - - #[cfg(feature = "cli")] - pub fn extend_with_platform(&mut self, platform: Platform) -> &mut Self { - let manifest = &self.manifest; - let features = match platform { - Platform::Web if manifest.features.contains_key("web") => { - vec!["web".to_string()] - } - Platform::Desktop if manifest.features.contains_key("desktop") => { - vec!["desktop".to_string()] - } - _ => { - // fullstack has its own feature insertion - we use a different featureset for the client and server - vec![] - } - }; - self.add_features(features); - self - } - - /// Check if assets should be pre_compressed. This will only be true in release mode if the user has enabled pre_compress in the web config. - pub fn should_pre_compress_web_assets(&self) -> bool { - self.dioxus_config.web.pre_compress && self.release - } -} - fn true_bool() -> bool { true } diff --git a/packages/cli-config/src/lib.rs b/packages/cli-config/src/lib.rs index 8fcc46138..b6054509b 100644 --- a/packages/cli-config/src/lib.rs +++ b/packages/cli-config/src/lib.rs @@ -8,37 +8,21 @@ pub use config::*; mod bundle; pub use bundle::*; -mod cargo; -pub use cargo::*; -#[cfg(feature = "cli")] mod serve; -#[cfg(feature = "cli")] pub use serve::*; -#[cfg(feature = "cli")] -mod settings; - -#[cfg(feature = "cli")] -pub use settings::*; - #[doc(hidden)] pub mod __private { - use crate::CrateConfig; + use crate::DioxusConfig; pub(crate) const CONFIG_ENV: &str = "DIOXUS_CONFIG"; pub(crate) const CONFIG_BASE_PATH_ENV: &str = "DIOXUS_CONFIG_BASE_PATH"; - pub fn save_config(config: &CrateConfig) -> CrateConfigDropGuard { + pub fn save_config(config: &DioxusConfig) -> CrateConfigDropGuard { std::env::set_var(CONFIG_ENV, serde_json::to_string(config).unwrap()); std::env::set_var( CONFIG_BASE_PATH_ENV, - config - .dioxus_config - .web - .app - .base_path - .clone() - .unwrap_or_default(), + config.web.app.base_path.clone().unwrap_or_default(), ); CrateConfigDropGuard } @@ -53,7 +37,7 @@ pub mod __private { } } - #[cfg(feature = "cli")] + #[cfg(feature = "read-config")] /// The environment variable that stores the CLIs serve configuration. /// We use this to communicate between the CLI and the server for fullstack applications. pub const SERVE_ENV: &str = "DIOXUS_SERVE_CONFIG"; @@ -74,7 +58,7 @@ impl std::error::Error for DioxusCLINotUsed {} #[cfg(feature = "read-config")] /// The current crate's configuration. pub static CURRENT_CONFIG: once_cell::sync::Lazy< - Result, + Result, > = once_cell::sync::Lazy::new(|| { CURRENT_CONFIG_JSON .and_then(|config| serde_json::from_str(config).ok()) diff --git a/packages/cli-config/src/serve.rs b/packages/cli-config/src/serve.rs index c3577c867..a8894c72a 100644 --- a/packages/cli-config/src/serve.rs +++ b/packages/cli-config/src/serve.rs @@ -1,35 +1,98 @@ +#![allow(unused)] // lots of configs... + +use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; + +#[cfg(feature = "read-from-args")] use clap::Parser; -/// Arguments for the serve command -#[derive(Clone, Debug, Parser, serde::Serialize, serde::Deserialize)] -pub struct ServeArguments { +/// The arguments for the address the server will run on + +#[cfg(feature = "read-from-args")] +#[derive(Clone, Debug, Parser)] +pub struct AddressArguments { /// The port the server will run on #[clap(long)] #[clap(default_value_t = default_port())] pub port: u16, + /// The address the server will run on - #[clap(long)] - pub addr: Option, + #[clap(long, default_value_t = default_address())] + pub addr: std::net::IpAddr, } -impl Default for ServeArguments { +#[cfg(feature = "read-from-args")] +impl Default for AddressArguments { fn default() -> Self { Self { port: default_port(), - addr: None, + addr: default_address(), } } } -impl ServeArguments { +#[cfg(feature = "read-from-args")] +impl AddressArguments { + /// Get the address the server should run on + pub fn address(&self) -> SocketAddr { + SocketAddr::new(self.addr, self.port) + } +} + +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct RuntimeCLIArguments { + /// The address hot reloading is running on + cli_address: SocketAddr, + + /// The address the server should run on + server_socket: Option, +} + +impl RuntimeCLIArguments { + /// Create a new RuntimeCLIArguments + pub fn new(cli_address: SocketAddr, server_socket: Option) -> Self { + Self { + cli_address, + server_socket, + } + } + /// Attempt to read the current serve settings from the CLI. This will only be set for the fullstack platform on recent versions of the CLI. pub fn from_cli() -> Option { std::env::var(crate::__private::SERVE_ENV) .ok() .and_then(|json| serde_json::from_str(&json).ok()) } + + /// Get the address the server should run on + pub fn server_socket(&self) -> Option { + self.server_socket + } + + /// Get the address the CLI is running on + pub fn cli_address(&self) -> SocketAddr { + self.cli_address + } + + /// Get the address the proxied fullstack server should run on + #[cfg(feature = "read-from-args")] + pub fn fullstack_address(&self) -> AddressArguments { + let socket = self.server_socket.unwrap_or_else(|| { + SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, default_port())) + }); + + AddressArguments { + port: socket.port(), + addr: socket.ip(), + } + } } +#[cfg(feature = "read-from-args")] fn default_port() -> u16 { 8080 } + +#[cfg(feature = "read-from-args")] +fn default_address() -> IpAddr { + IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)) +} diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 3e7262fe8..853d5dbde 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -25,26 +25,30 @@ serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" toml = { workspace = true } fs_extra = "1.2.0" -cargo_toml = "0.18.0" +cargo_toml = { workspace = true } futures-util = { workspace = true, features = ["async-await-macro"] } -notify = { version = "5.0.0-pre.16", features = ["serde"] } +notify = { workspace = true, features = ["serde"] } html_parser = { workspace = true } cargo_metadata = "0.18.1" -tokio = { version = "1.16.1", features = ["fs", "sync", "rt", "macros"] } +tokio = { version = "1.16.1", features = ["fs", "sync", "rt", "macros", "process", "rt-multi-thread"] } +tokio-stream = "0.1.15" atty = "0.2.14" chrono = "0.4.19" anyhow = "1" hyper = { workspace = true } hyper-util = "0.1.3" hyper-rustls = { workspace = true } -indicatif = "0.17.5" +rustls = "0.23.11" subprocess = "0.2.9" rayon = "1.8.0" - +futures-channel = { workspace = true } +krates = { version = "0.17.0" } axum = { workspace = true, features = ["ws"] } axum-server = { workspace = true, features = ["tls-rustls"] } axum-extra = { workspace = true, features = ["typed-header"] } tower-http = { workspace = true, features = ["full"] } +proc-macro2 = { workspace = true, features = ["span-locations"] } +syn = { workspace = true, features = ["full", "extra-traits", "visit", "visit-mut"] } headers = "0.3.7" walkdir = "2" @@ -61,55 +65,55 @@ flate2 = "1.0.22" tar = "0.4.38" zip = "0.6.2" tower = { workspace = true } -lazy_static = "1.4.0" +once_cell = "1.19.0" # plugin packages -mlua = { version = "0.8.1", features = [ - "lua54", - "vendored", - "async", - "send", - "macros", -], optional = true } ctrlc = "3.2.3" open = "5.0.1" -cargo-generate = "0.20.0" -toml_edit = "0.21.0" +cargo-generate = "=0.21.1" +toml_edit = "0.22.15" # bundling -tauri-bundler = { version = "=1.4.*", features = ["native-tls-vendored"] } +tauri-bundler = { workspace = true } # formatting -syn = { workspace = true } +# syn = { workspace = true } prettyplease = { workspace = true } # Assets manganis-cli-support = { workspace = true, features = ["html"] } -brotli = "5.0.0" +brotli = "6.0.0" dioxus-autofmt = { workspace = true } dioxus-check = { workspace = true } rsx-rosetta = { workspace = true } -dioxus-rsx = { workspace = true } +dioxus-rsx = { workspace = true, features = ["serde"]} dioxus-html = { workspace = true, features = ["hot-reload-context"] } dioxus-core = { workspace = true, features = ["serialize"] } dioxus-hot-reload = { workspace = true, features = ["serve"] } -interprocess = { workspace = true } -# interprocess-docfix = { version = "1.2.2" } ignore = "0.4.22" env_logger = "0.11.3" tracing-subscriber = { version = "0.3.18", features = ["std", "env-filter"] } +console-subscriber = { version = "0.3.0", optional = true } tracing = { workspace = true } wasm-opt = "0.116.1" +ratatui = { version = "0.27.0", features = ["crossterm", "unstable"] } +crossterm = { version = "0.27.0", features = ["event-stream"] } +ansi-to-tui = "=5.0.0-rc.1" +ansi-to-html = "0.2.1" # on maco, we need to specify the vendored feature on ssl when cross compiling -[target.'cfg(target_os = "macos")'.dependencies] -openssl = { version = "0.10", features = ["vendored"] } +# [target.'cfg(target_os = "macos")'.dependencies] +# openssl = { version = "0.10", features = ["vendored"] } + +[build-dependencies] +built = { version = "=0.7.3", features = ["git2"] } [features] default = [] -plugin = ["dep:mlua"] +plugin = [] +tokio-console = ["dep:console-subscriber"] [[bin]] path = "src/main.rs" diff --git a/packages/cli/src/assets/dioxus.toml b/packages/cli/assets/dioxus.toml similarity index 95% rename from packages/cli/src/assets/dioxus.toml rename to packages/cli/assets/dioxus.toml index 486e65a13..7602f6561 100644 --- a/packages/cli/src/assets/dioxus.toml +++ b/packages/cli/assets/dioxus.toml @@ -47,12 +47,6 @@ script = [] # serve: [dev-server] only script = [] -[application.plugins] - -available = true - -required = [] - [bundler] # Bundle identifier identifier = "io.github.{{project-name}}" diff --git a/packages/cli/src/assets/icon.ico b/packages/cli/assets/icon.ico similarity index 100% rename from packages/cli/src/assets/icon.ico rename to packages/cli/assets/icon.ico diff --git a/packages/cli/assets/index.html b/packages/cli/assets/index.html new file mode 100644 index 000000000..e07ae8304 --- /dev/null +++ b/packages/cli/assets/index.html @@ -0,0 +1,12 @@ + + + + {app_title} + + + + + +
+ + diff --git a/packages/cli/assets/loading.html b/packages/cli/assets/loading.html new file mode 100644 index 000000000..f4c1cdb52 --- /dev/null +++ b/packages/cli/assets/loading.html @@ -0,0 +1,118 @@ + + + + Dioxus Build In Progress + + + + + +
+

Building project 0%

+
+ + + diff --git a/packages/cli/build.rs b/packages/cli/build.rs new file mode 100644 index 000000000..d8f91cb91 --- /dev/null +++ b/packages/cli/build.rs @@ -0,0 +1,3 @@ +fn main() { + built::write_built_file().expect("Failed to acquire build-time information"); +} diff --git a/packages/cli/src/assets.rs b/packages/cli/src/assets.rs index 8bb6d0b21..f28eadfa4 100644 --- a/packages/cli/src/assets.rs +++ b/packages/cli/src/assets.rs @@ -1,20 +1,21 @@ +use crate::builder::{BuildMessage, MessageType, Stage, UpdateBuildProgress, UpdateStage}; +use crate::dioxus_crate::DioxusCrate; +use crate::Result; +use anyhow::Context; use brotli::enc::BrotliEncoderParams; +use futures_channel::mpsc::UnboundedSender; +use manganis_cli_support::{process_file, AssetManifest, AssetManifestExt, AssetType}; use std::fs; use std::path::Path; use std::{ffi::OsString, path::PathBuf}; -use walkdir::WalkDir; - use std::{fs::File, io::Write}; - -use crate::Result; -use dioxus_cli_config::CrateConfig; -use dioxus_cli_config::Platform; -use manganis_cli_support::{AssetManifest, AssetManifestExt}; +use tracing::Level; +use walkdir::WalkDir; /// The temp file name for passing manganis json from linker to current exec. pub const MG_JSON_OUT: &str = "mg-out"; -pub fn asset_manifest(config: &CrateConfig) -> AssetManifest { +pub fn asset_manifest(config: &DioxusCrate) -> AssetManifest { let file_path = config.out_dir().join(MG_JSON_OUT); let read = fs::read_to_string(&file_path).unwrap(); _ = fs::remove_file(file_path); @@ -24,14 +25,18 @@ pub fn asset_manifest(config: &CrateConfig) -> AssetManifest { } /// Create a head file that contains all of the imports for assets that the user project uses -pub fn create_assets_head(config: &CrateConfig, manifest: &AssetManifest) -> Result<()> { +pub fn create_assets_head(config: &DioxusCrate, manifest: &AssetManifest) -> Result<()> { let mut file = File::create(config.out_dir().join("__assets_head.html"))?; file.write_all(manifest.head().as_bytes())?; Ok(()) } /// Process any assets collected from the binary -pub(crate) fn process_assets(config: &CrateConfig, manifest: &AssetManifest) -> anyhow::Result<()> { +pub(crate) fn process_assets( + config: &DioxusCrate, + manifest: &AssetManifest, + progress: &mut UnboundedSender, +) -> anyhow::Result<()> { let static_asset_output_dir = PathBuf::from( config .dioxus_config @@ -43,7 +48,44 @@ pub(crate) fn process_assets(config: &CrateConfig, manifest: &AssetManifest) -> ); let static_asset_output_dir = config.out_dir().join(static_asset_output_dir); - manifest.copy_static_assets_to(static_asset_output_dir)?; + std::fs::create_dir_all(&static_asset_output_dir) + .context("Failed to create static asset output directory")?; + + let mut assets_finished: usize = 0; + let assets = manifest.assets(); + let asset_count = assets.len(); + assets.iter().try_for_each(move |asset| { + if let AssetType::File(file_asset) = asset { + match process_file(file_asset, &static_asset_output_dir) { + Ok(_) => { + // Update the progress + _ = progress.start_send(UpdateBuildProgress { + stage: Stage::OptimizingAssets, + update: UpdateStage::AddMessage(BuildMessage { + level: Level::INFO, + message: MessageType::Text(format!( + "Optimized static asset {}", + file_asset + )), + source: None, + }), + }); + assets_finished += 1; + _ = progress.start_send(UpdateBuildProgress { + stage: Stage::OptimizingAssets, + update: UpdateStage::SetProgress( + assets_finished as f64 / asset_count as f64, + ), + }); + } + Err(err) => { + tracing::error!("Failed to copy static asset: {}", err); + return Err(err); + } + } + } + Ok::<(), anyhow::Error>(()) + })?; Ok(()) } @@ -68,21 +110,11 @@ impl Drop for AssetConfigDropGuard { } } -pub fn copy_assets_dir(config: &CrateConfig, platform: Platform) -> anyhow::Result<()> { - tracing::info!("Copying public assets to the output directory..."); - let out_dir = config.out_dir(); - let asset_dir = config.asset_dir(); - - if asset_dir.is_dir() { - // Only pre-compress the assets from the web build. Desktop assets are not served, so they don't need to be pre_compressed - let pre_compress = platform == Platform::Web && config.should_pre_compress_web_assets(); - - copy_dir_to(asset_dir, out_dir, pre_compress)?; - } - Ok(()) -} - -fn copy_dir_to(src_dir: PathBuf, dest_dir: PathBuf, pre_compress: bool) -> std::io::Result<()> { +pub(crate) fn copy_dir_to( + src_dir: PathBuf, + dest_dir: PathBuf, + pre_compress: bool, +) -> std::io::Result<()> { let entries = std::fs::read_dir(&src_dir)?; let mut children: Vec>> = Vec::new(); diff --git a/packages/cli/src/assets/index.html b/packages/cli/src/assets/index.html deleted file mode 100644 index b39b673dc..000000000 --- a/packages/cli/src/assets/index.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - {app_title} - - - - - - {style_include} - - -
- - {script_include} - - diff --git a/packages/cli/src/builder.rs b/packages/cli/src/builder.rs deleted file mode 100644 index 39a36a03a..000000000 --- a/packages/cli/src/builder.rs +++ /dev/null @@ -1,847 +0,0 @@ -use crate::{ - assets::{ - asset_manifest, copy_assets_dir, create_assets_head, pre_compress_folder, process_assets, - AssetConfigDropGuard, - }, - error::{Error, Result}, - link::LinkCommand, - tools::Tool, -}; -use anyhow::Context; -use cargo_metadata::{diagnostic::Diagnostic, Message}; -use dioxus_cli_config::{crate_root, CrateConfig, ExecutableType, WasmOptLevel}; -use indicatif::{ProgressBar, ProgressStyle}; -use lazy_static::lazy_static; -use manganis_cli_support::{AssetManifest, ManganisSupportGuard}; -use std::{ - env, - fs::{copy, create_dir_all, File}, - io::{self, IsTerminal, Read}, - panic, - path::PathBuf, - process::Command, - time::Duration, -}; -use wasm_bindgen_cli_support::Bindgen; - -lazy_static! { - static ref PROGRESS_BARS: indicatif::MultiProgress = indicatif::MultiProgress::new(); -} - -#[derive(Debug, Clone)] -pub struct BuildResult { - pub warnings: Vec, - pub executable: Option, - pub elapsed_time: u128, - pub assets: Option, -} - -/// This trait is only created for the convenient and concise way to set -/// `RUSTFLAGS` environment variable for the `subprocess::Exec`. -pub trait ExecWithRustFlagsSetter { - fn set_rust_flags(self, rust_flags: Option) -> Self; -} - -impl ExecWithRustFlagsSetter for subprocess::Exec { - /// Sets (appends to, if already set) `RUSTFLAGS` environment variable if - /// `rust_flags` is not `None`. - fn set_rust_flags(self, rust_flags: Option) -> Self { - if let Some(rust_flags) = rust_flags { - // Some `RUSTFLAGS` might be already set in the environment or provided - // by the user. They should take higher priority than the default flags. - // If no default flags are provided, then there is no point in - // redefining the environment variable with the same value, if it is - // even set. If no custom flags are set, then there is no point in - // adding the unnecessary whitespace to the command. - self.env( - "RUSTFLAGS", - if let Ok(custom_rust_flags) = env::var("RUSTFLAGS") { - rust_flags + " " + custom_rust_flags.as_str() - } else { - rust_flags - }, - ) - } else { - self - } - } -} - -/// Build client (WASM). -/// Note: `rust_flags` argument is only used for the fullstack platform. -pub fn build_web( - config: &CrateConfig, - skip_assets: bool, - rust_flags: Option, -) -> Result { - // [1] Build the project with cargo, generating a wasm32-unknown-unknown target (is there a more specific, better target to leverage?) - // [2] Generate the appropriate build folders - // [3] Wasm-bindgen the .wasm file, and move it into the {builddir}/modules/xxxx/xxxx_bg.wasm - // [4] Wasm-opt the .wasm file with whatever optimizations need to be done - // [5][OPTIONAL] Builds the Tailwind CSS file using the Tailwind standalone binary - // [6] Link up the html page to the wasm module - - let CrateConfig { - crate_dir, - target_dir, - dioxus_config, - .. - } = config; - let out_dir = config.out_dir(); - - let _asset_guard = AssetConfigDropGuard::new(); - let _manganis_support = ManganisSupportGuard::default(); - - // start to build the assets - build_assets(config)?; - - let t_start = std::time::Instant::now(); - let _guard = dioxus_cli_config::__private::save_config(config); - - // [1] Build the .wasm module - tracing::info!("🚅 Running build command..."); - - // If the user has rustup, we can check if the wasm32-unknown-unknown target is installed - // Otherwise we can just assume it is installed - which i snot great... - // Eventually we can poke at the errors and let the user know they need to install the target - if let Ok(wasm_check_command) = Command::new("rustup").args(["show"]).output() { - let wasm_check_output = String::from_utf8(wasm_check_command.stdout).unwrap(); - if !wasm_check_output.contains("wasm32-unknown-unknown") { - tracing::info!("wasm32-unknown-unknown target not detected, installing.."); - let _ = Command::new("rustup") - .args(["target", "add", "wasm32-unknown-unknown"]) - .output()?; - } - } - - let mut cargo_args = vec!["--target".to_string(), "wasm32-unknown-unknown".to_string()]; - - let mut cmd = subprocess::Exec::cmd("cargo") - .set_rust_flags(rust_flags) - .env("CARGO_TARGET_DIR", target_dir) - .cwd(crate_dir) - .arg("build") - .arg("--message-format=json-render-diagnostics"); - - // TODO: make the initial variable mutable to simplify all the expressions - // below. Look inside the `build_desktop()` as an example. - if config.release { - cargo_args.push("--release".to_string()); - } - if config.verbose { - cargo_args.push("--verbose".to_string()); - } else { - cargo_args.push("--quiet".to_string()); - } - - if config.custom_profile.is_some() { - let custom_profile = config.custom_profile.as_ref().unwrap(); - cargo_args.push("--profile".to_string()); - cargo_args.push(custom_profile.to_string()); - } - - if config.features.is_some() { - let features_str = config.features.as_ref().unwrap().join(" "); - cargo_args.push("--features".to_string()); - cargo_args.push(features_str); - } - - if let Some(target) = &config.target { - cargo_args.push("--target".to_string()); - cargo_args.push(target.clone()); - } - - cargo_args.append(&mut config.cargo_args.clone()); - - match &config.executable { - ExecutableType::Binary(name) => { - cargo_args.push("--bin".to_string()); - cargo_args.push(name.to_string()); - } - ExecutableType::Lib(name) => { - cargo_args.push("--lib".to_string()); - cargo_args.push(name.to_string()); - } - ExecutableType::Example(name) => { - cargo_args.push("--example".to_string()); - cargo_args.push(name.to_string()); - } - }; - - cmd = cmd.args(&cargo_args); - let CargoBuildResult { - warnings, - output_location, - } = prettier_build(cmd)?; - - // Start Manganis linker intercept. - let linker_args = vec![format!("{}", config.out_dir().display())]; - - manganis_cli_support::start_linker_intercept( - &LinkCommand::command_name(), - cargo_args, - Some(linker_args), - ) - .unwrap(); - - let output_location = output_location.context("No output location found")?; - - // [2] Establish the output directory structure - let bindgen_outdir = out_dir.join("assets").join("dioxus"); - - let input_path = output_location.with_extension("wasm"); - - tracing::info!("Running wasm-bindgen"); - let run_wasm_bindgen = || { - // [3] Bindgen the final binary for use easy linking - let mut bindgen_builder = Bindgen::new(); - - let keep_debug = dioxus_config.web.wasm_opt.debug || (!config.release); - - bindgen_builder - .input_path(&input_path) - .web(true) - .unwrap() - .debug(keep_debug) - .demangle(keep_debug) - .keep_debug(keep_debug) - .reference_types(true) - .remove_name_section(!keep_debug) - .remove_producers_section(!keep_debug) - .out_name(&dioxus_config.application.name) - .generate(&bindgen_outdir) - .unwrap(); - }; - let bindgen_result = panic::catch_unwind(run_wasm_bindgen); - - // WASM bindgen requires the exact version of the bindgen schema to match the version the CLI was built with - // If we get an error, we can try to recover by pinning the user's wasm-bindgen version to the version we used - if let Err(err) = bindgen_result { - tracing::error!("Bindgen build failed: {:?}", err); - update_wasm_bindgen_version()?; - run_wasm_bindgen(); - } - - // Run wasm-opt if this is a release build - if config.release { - tracing::info!("Running optimization with wasm-opt..."); - let mut options = match dioxus_config.web.wasm_opt.level { - WasmOptLevel::Z => wasm_opt::OptimizationOptions::new_optimize_for_size_aggressively(), - WasmOptLevel::S => wasm_opt::OptimizationOptions::new_optimize_for_size(), - WasmOptLevel::Zero => wasm_opt::OptimizationOptions::new_opt_level_0(), - WasmOptLevel::One => wasm_opt::OptimizationOptions::new_opt_level_1(), - WasmOptLevel::Two => wasm_opt::OptimizationOptions::new_opt_level_2(), - WasmOptLevel::Three => wasm_opt::OptimizationOptions::new_opt_level_3(), - WasmOptLevel::Four => wasm_opt::OptimizationOptions::new_opt_level_4(), - }; - let wasm_file = bindgen_outdir.join(format!("{}_bg.wasm", dioxus_config.application.name)); - let old_size = wasm_file.metadata()?.len(); - options - // WASM bindgen relies on reference types - .enable_feature(wasm_opt::Feature::ReferenceTypes) - .debug_info(dioxus_config.web.wasm_opt.debug) - .run(&wasm_file, &wasm_file) - .map_err(|err| Error::Other(anyhow::anyhow!(err)))?; - let new_size = wasm_file.metadata()?.len(); - tracing::info!( - "wasm-opt reduced WASM size from {} to {} ({:2}%)", - old_size, - new_size, - (new_size as f64 - old_size as f64) / old_size as f64 * 100.0 - ); - } - - // If pre-compressing is enabled, we can pre_compress the wasm-bindgen output - pre_compress_folder(&bindgen_outdir, config.should_pre_compress_web_assets())?; - - // [5][OPTIONAL] If tailwind is enabled and installed we run it to generate the CSS - let dioxus_tools = dioxus_config.application.tools.clone(); - if dioxus_tools.contains_key("tailwindcss") { - let info = dioxus_tools.get("tailwindcss").unwrap(); - let tailwind = crate::tools::Tool::Tailwind; - - if tailwind.is_installed() { - if let Some(sub) = info.as_table() { - tracing::info!("Building Tailwind bundle CSS file..."); - - let input_path = match sub.get("input") { - Some(val) => val.as_str().unwrap(), - None => "./public", - }; - let config_path = match sub.get("config") { - Some(val) => val.as_str().unwrap(), - None => "./src/tailwind.config.js", - }; - let mut args = vec![ - "-i", - input_path, - "-o", - "dist/tailwind.css", - "-c", - config_path, - ]; - - if config.release { - args.push("--minify"); - } - - tailwind.call("tailwindcss", args)?; - } - } else { - tracing::warn!( - "Tailwind tool not found, you can use `dx tool add tailwindcss` to install it." - ); - } - } - - // this code will copy all public file to the output dir - copy_assets_dir(config, dioxus_cli_config::Platform::Web)?; - - let assets = if !skip_assets { - tracing::info!("Processing assets"); - let assets = asset_manifest(config); - process_assets(config, &assets)?; - Some(assets) - } else { - None - }; - - Ok(BuildResult { - warnings, - executable: Some(output_location), - elapsed_time: t_start.elapsed().as_millis(), - assets, - }) -} - -// Attempt to automatically recover from a bindgen failure by updating the wasm-bindgen version -fn update_wasm_bindgen_version() -> Result<()> { - let cli_bindgen_version = wasm_bindgen_shared::version(); - tracing::info!("Attempting to recover from bindgen failure by setting the wasm-bindgen version to {cli_bindgen_version}..."); - - let output = Command::new("cargo") - .args([ - "update", - "-p", - "wasm-bindgen", - "--precise", - &cli_bindgen_version, - ]) - .output(); - let mut error_message = None; - if let Ok(output) = output { - if output.status.success() { - tracing::info!("Successfully updated wasm-bindgen to {cli_bindgen_version}"); - return Ok(()); - } else { - error_message = Some(output); - } - } - - if let Some(output) = error_message { - tracing::error!("Failed to update wasm-bindgen: {:#?}", output); - } - - Err(Error::BuildFailed(format!("WASM bindgen build failed!\nThis is probably due to the Bindgen version, dioxus-cli is using `{cli_bindgen_version}` which is not compatible with your crate.\nPlease reinstall the dioxus cli to fix this issue.\nYou can reinstall the dioxus cli by running `cargo install dioxus-cli --force` and then rebuild your project"))) -} - -/// Note: `rust_flags` argument is only used for the fullstack platform -/// (server). -pub fn build_desktop( - config: &CrateConfig, - _is_serve: bool, - skip_assets: bool, - rust_flags: Option, -) -> Result { - tracing::info!("🚅 Running build [Desktop] command..."); - - let t_start = std::time::Instant::now(); - build_assets(config)?; - let _guard = dioxus_cli_config::__private::save_config(config); - let _manganis_support = ManganisSupportGuard::default(); - let _asset_guard = AssetConfigDropGuard::new(); - - let mut cargo_args = Vec::new(); - - let mut cmd = subprocess::Exec::cmd("cargo") - .set_rust_flags(rust_flags) - .env("CARGO_TARGET_DIR", &config.target_dir) - .cwd(&config.crate_dir) - .arg("build") - .arg("--message-format=json-render-diagnostics"); - - if config.release { - cargo_args.push("--release".to_string()); - } - if config.verbose { - cargo_args.push("--verbose".to_string()); - } else { - cargo_args.push("--quiet".to_string()); - } - - if config.custom_profile.is_some() { - let custom_profile = config.custom_profile.as_ref().unwrap(); - cargo_args.push("--profile".to_string()); - cargo_args.push(custom_profile.to_string()); - } - - if config.features.is_some() { - let features_str = config.features.as_ref().unwrap().join(" "); - cargo_args.push("--features".to_string()); - cargo_args.push(features_str); - } - - if let Some(target) = &config.target { - cargo_args.push("--target".to_string()); - cargo_args.push(target.clone()); - } - - cargo_args.append(&mut config.cargo_args.clone()); - - match &config.executable { - ExecutableType::Binary(name) => { - cargo_args.push("--bin".to_string()); - cargo_args.push(name.to_string()); - } - ExecutableType::Lib(name) => { - cargo_args.push("--lib".to_string()); - cargo_args.push(name.to_string()); - } - ExecutableType::Example(name) => { - cargo_args.push("--example".to_string()); - cargo_args.push(name.to_string()); - } - }; - - cmd = cmd.args(&cargo_args); - let warning_messages = prettier_build(cmd)?; - - // Start Manganis linker intercept. - let linker_args = vec![format!("{}", config.out_dir().display())]; - - manganis_cli_support::start_linker_intercept( - &LinkCommand::command_name(), - cargo_args, - Some(linker_args), - )?; - - let file_name: String = config.executable.executable().unwrap().to_string(); - - let target_file = if cfg!(windows) { - format!("{}.exe", &file_name) - } else { - file_name - }; - - if !config.out_dir().is_dir() { - create_dir_all(config.out_dir())?; - } - let output_path = config.out_dir().join(target_file); - if let Some(res_path) = &warning_messages.output_location { - copy(res_path, &output_path)?; - } - - copy_assets_dir(config, dioxus_cli_config::Platform::Desktop)?; - - let assets = if !skip_assets { - tracing::info!("Processing assets"); - let assets = asset_manifest(config); - // Collect assets - process_assets(config, &assets)?; - // Create the __assets_head.html file for bundling - create_assets_head(config, &assets)?; - Some(assets) - } else { - None - }; - - tracing::info!( - "🚩 Build completed: [./{}]", - config.dioxus_config.application.out_dir.clone().display() - ); - - println!("build desktop done"); - - Ok(BuildResult { - warnings: warning_messages.warnings, - executable: Some(output_path), - elapsed_time: t_start.elapsed().as_millis(), - assets, - }) -} - -struct CargoBuildResult { - warnings: Vec, - output_location: Option, -} - -struct Outputter { - progress_bar: Option, -} - -impl Outputter { - pub fn new() -> Self { - let stdout = io::stdout().lock(); - - let mut myself = Self { progress_bar: None }; - - if stdout.is_terminal() { - let mut pb = ProgressBar::new_spinner(); - pb.enable_steady_tick(Duration::from_millis(200)); - pb = PROGRESS_BARS.add(pb); - pb.set_style( - ProgressStyle::with_template("{spinner:.dim.bold} {wide_msg}") - .unwrap() - .tick_chars("/|\\- "), - ); - - myself.progress_bar = Some(pb); - } - - myself - } - - pub fn println(&self, msg: impl ToString) { - let msg = msg.to_string(); - if let Some(pb) = &self.progress_bar { - pb.set_message(msg) - } else { - println!("{msg}"); - } - } - - pub fn finish_with_message(&self, msg: impl ToString) { - let msg = msg.to_string(); - if let Some(pb) = &self.progress_bar { - pb.finish_with_message(msg) - } else { - println!("{msg}"); - } - } -} - -fn prettier_build(cmd: subprocess::Exec) -> anyhow::Result { - let mut warning_messages: Vec = vec![]; - - let output = Outputter::new(); - output.println("💼 Waiting to start building the project..."); - - let stdout = cmd.detached().stream_stdout()?; - let reader = std::io::BufReader::new(stdout); - let mut output_location = None; - - for message in cargo_metadata::Message::parse_stream(reader) { - match message.unwrap() { - Message::CompilerMessage(msg) => { - let message = msg.message; - match message.level { - cargo_metadata::diagnostic::DiagnosticLevel::Error => { - return { - Err(anyhow::anyhow!(message - .rendered - .unwrap_or("Unknown".into()))) - }; - } - cargo_metadata::diagnostic::DiagnosticLevel::Warning => { - warning_messages.push(message.clone()); - } - _ => {} - } - } - Message::CompilerArtifact(artifact) => { - output.println(format!("⚙ Compiling {} ", artifact.package_id)); - if let Some(executable) = artifact.executable { - output_location = Some(executable.into()); - } - } - Message::BuildScriptExecuted(script) => { - let _package_id = script.package_id.to_string(); - } - Message::BuildFinished(finished) => { - if finished.success { - output.finish_with_message("👑 Build done."); - } else { - output.finish_with_message("❌ Build failed."); - return Err(anyhow::anyhow!("Build failed")); - } - } - _ => { - // Unknown message - } - } - } - - Ok(CargoBuildResult { - warnings: warning_messages, - output_location, - }) -} - -pub fn gen_page(config: &CrateConfig, manifest: Option<&AssetManifest>, serve: bool) -> String { - let _guard = AssetConfigDropGuard::new(); - - let crate_root = crate_root().unwrap(); - let custom_html_file = crate_root.join("index.html"); - let mut html = if custom_html_file.is_file() { - let mut buf = String::new(); - let mut file = File::open(custom_html_file).unwrap(); - if file.read_to_string(&mut buf).is_ok() { - buf - } else { - String::from(include_str!("./assets/index.html")) - } - } else { - String::from(include_str!("./assets/index.html")) - }; - - let resources = config.dioxus_config.web.resource.clone(); - - let mut style_list = resources.style.unwrap_or_default(); - let mut script_list = resources.script.unwrap_or_default(); - - if serve { - let mut dev_style = resources.dev.style.clone(); - let mut dev_script = resources.dev.script.clone(); - style_list.append(&mut dev_style); - script_list.append(&mut dev_script); - } - - let mut style_str = String::new(); - for style in style_list { - style_str.push_str(&format!( - "\n", - &style.to_str().unwrap(), - )) - } - if config - .dioxus_config - .application - .tools - .clone() - .contains_key("tailwindcss") - { - style_str.push_str("\n"); - } - if let Some(manifest) = manifest { - style_str.push_str(&manifest.head()); - } - - replace_or_insert_before("{style_include}", &style_str, "\n", - &script.to_str().unwrap(), - )) - } - - replace_or_insert_before("{script_include}", &script_str, "{}", dioxus_hot_reload::RECONNECT_SCRIPT); - } - - let base_path = match &config.dioxus_config.web.app.base_path { - Some(path) => path.trim_matches('/'), - None => ".", - }; - let app_name = &config.dioxus_config.application.name; - // Check if a script already exists - if html.contains("{app_name}") && html.contains("{base_path}") { - html = html.replace("{app_name}", app_name); - - html = html.replace("{base_path}", base_path); - } else { - // If not, insert the script - html = html.replace( - " - import init from "/{base_path}/assets/dioxus/{app_name}.js"; - init("/{base_path}/assets/dioxus/{app_name}_bg.wasm").then(wasm => {{ - if (wasm.__wbindgen_start == undefined) {{ - wasm.main(); - }} - }}); - - - - Result> { - let mut result = vec![]; - - let dioxus_config = &config.dioxus_config; - let dioxus_tools = dioxus_config.application.tools.clone(); - - // check sass tool state - let sass = Tool::Sass; - if sass.is_installed() && dioxus_tools.contains_key("sass") { - let sass_conf = dioxus_tools.get("sass").unwrap(); - if let Some(tab) = sass_conf.as_table() { - let source_map = tab.contains_key("source_map"); - let source_map = if source_map && tab.get("source_map").unwrap().is_bool() { - if tab.get("source_map").unwrap().as_bool().unwrap_or_default() { - "--source-map" - } else { - "--no-source-map" - } - } else { - "--source-map" - }; - - if tab.contains_key("input") { - if tab.get("input").unwrap().is_str() { - let file = tab.get("input").unwrap().as_str().unwrap().trim(); - - if file == "*" { - // if the sass open auto, we need auto-check the assets dir. - let asset_dir = config.asset_dir().clone(); - if asset_dir.is_dir() { - for entry in walkdir::WalkDir::new(&asset_dir) - .into_iter() - .filter_map(|e| e.ok()) - { - let temp = entry.path(); - if temp.is_file() { - let suffix = temp.extension(); - if suffix.is_none() { - continue; - } - let suffix = suffix.unwrap().to_str().unwrap(); - if suffix == "scss" || suffix == "sass" { - // if file suffix is `scss` / `sass` we need transform it. - let out_file = format!( - "{}.css", - temp.file_stem().unwrap().to_str().unwrap() - ); - let target_path = config - .out_dir() - .join( - temp.strip_prefix(&asset_dir) - .unwrap() - .parent() - .unwrap(), - ) - .join(out_file); - let res = sass.call( - "sass", - vec![ - temp.to_str().unwrap(), - target_path.to_str().unwrap(), - source_map, - ], - ); - if res.is_ok() { - result.push(temp.to_path_buf()); - } - } - } - } - } - } else { - // just transform one file. - let relative_path = if &file[0..1] == "/" { - &file[1..file.len()] - } else { - file - }; - let path = config.asset_dir().join(relative_path); - let out_file = - format!("{}.css", path.file_stem().unwrap().to_str().unwrap()); - let target_path = config - .out_dir() - .join(PathBuf::from(relative_path).parent().unwrap()) - .join(out_file); - if path.is_file() { - let res = sass.call( - "sass", - vec![ - path.to_str().unwrap(), - target_path.to_str().unwrap(), - source_map, - ], - ); - if res.is_ok() { - result.push(path); - } else { - tracing::error!("{:?}", res); - } - } - } - } else if tab.get("input").unwrap().is_array() { - // check files list. - let list = tab.get("input").unwrap().as_array().unwrap(); - for i in list { - if i.is_str() { - let path = i.as_str().unwrap(); - let relative_path = if &path[0..1] == "/" { - &path[1..path.len()] - } else { - path - }; - let path = config.asset_dir().join(relative_path); - let out_file = - format!("{}.css", path.file_stem().unwrap().to_str().unwrap()); - let target_path = config - .out_dir() - .join(PathBuf::from(relative_path).parent().unwrap()) - .join(out_file); - if path.is_file() { - let res = sass.call( - "sass", - vec![ - path.to_str().unwrap(), - target_path.to_str().unwrap(), - source_map, - ], - ); - if res.is_ok() { - result.push(path); - } - } - } - } - } - } - } - } - // SASS END - - Ok(result) -} diff --git a/packages/cli/src/builder/cargo.rs b/packages/cli/src/builder/cargo.rs new file mode 100644 index 000000000..0439163ba --- /dev/null +++ b/packages/cli/src/builder/cargo.rs @@ -0,0 +1,224 @@ +use super::web::install_web_build_tooling; +use super::BuildRequest; +use super::BuildResult; +use crate::assets::copy_dir_to; +use crate::assets::create_assets_head; +use crate::assets::{asset_manifest, process_assets, AssetConfigDropGuard}; +use crate::builder::progress::build_cargo; +use crate::builder::progress::CargoBuildResult; +use crate::builder::progress::Stage; +use crate::builder::progress::UpdateBuildProgress; +use crate::builder::progress::UpdateStage; +use crate::link::LinkCommand; +use crate::Result; +use anyhow::Context; +use futures_channel::mpsc::UnboundedSender; +use manganis_cli_support::ManganisSupportGuard; +use std::fs::create_dir_all; + +impl BuildRequest { + /// Create a list of arguments for cargo builds + pub(crate) fn build_arguments(&self) -> Vec { + let mut cargo_args = Vec::new(); + + if self.build_arguments.release { + cargo_args.push("--release".to_string()); + } + if self.build_arguments.verbose { + cargo_args.push("--verbose".to_string()); + } else { + cargo_args.push("--quiet".to_string()); + } + + if let Some(custom_profile) = &self.build_arguments.profile { + cargo_args.push("--profile".to_string()); + cargo_args.push(custom_profile.to_string()); + } + + if !self.build_arguments.target_args.features.is_empty() { + let features_str = self.build_arguments.target_args.features.join(" "); + cargo_args.push("--features".to_string()); + cargo_args.push(features_str); + } + + if let Some(target) = self.web.then_some("wasm32-unknown-unknown").or(self + .build_arguments + .target_args + .target + .as_deref()) + { + cargo_args.push("--target".to_string()); + cargo_args.push(target.to_string()); + } + + cargo_args.append(&mut self.build_arguments.cargo_args.clone()); + + match self.dioxus_crate.executable_type() { + krates::cm::TargetKind::Bin => { + cargo_args.push("--bin".to_string()); + } + krates::cm::TargetKind::Lib => { + cargo_args.push("--lib".to_string()); + } + krates::cm::TargetKind::Example => { + cargo_args.push("--example".to_string()); + } + _ => {} + }; + cargo_args.push(self.dioxus_crate.executable_name().to_string()); + + cargo_args + } + + /// Create a build command for cargo + fn prepare_build_command(&self) -> Result<(tokio::process::Command, Vec)> { + let mut cmd = tokio::process::Command::new("cargo"); + cmd.arg("rustc"); + if let Some(target_dir) = &self.target_dir { + cmd.env("CARGO_TARGET_DIR", target_dir); + } + cmd.current_dir(self.dioxus_crate.crate_dir()) + .arg("--message-format") + .arg("json-diagnostic-rendered-ansi"); + + let cargo_args = self.build_arguments(); + cmd.args(&cargo_args); + + cmd.arg("--").args(self.rust_flags.clone()); + + Ok((cmd, cargo_args)) + } + + pub async fn build( + &self, + mut progress: UnboundedSender, + ) -> Result { + tracing::info!("🚅 Running build [Desktop] command..."); + + // Set up runtime guards + let _guard = dioxus_cli_config::__private::save_config(&self.dioxus_crate.dioxus_config); + let _manganis_support = ManganisSupportGuard::default(); + let _asset_guard = AssetConfigDropGuard::new(); + + // If this is a web, build make sure we have the web build tooling set up + if self.web { + install_web_build_tooling(&mut progress).await?; + } + + // Create the build command + let (cmd, cargo_args) = self.prepare_build_command()?; + + // Run the build command with a pretty loader + let crate_count = self.get_unit_count_estimate().await; + let cargo_result = build_cargo(crate_count, cmd, &mut progress).await?; + + // Post process the build result + let build_result = self + .post_process_build(cargo_args, &cargo_result, &mut progress) + .await + .context("Failed to post process build")?; + + tracing::info!( + "🚩 Build completed: [./{}]", + self.dioxus_crate + .dioxus_config + .application + .out_dir + .clone() + .display() + ); + + _ = progress.start_send(UpdateBuildProgress { + stage: Stage::Finished, + update: UpdateStage::Start, + }); + + Ok(build_result) + } + + async fn post_process_build( + &self, + cargo_args: Vec, + cargo_build_result: &CargoBuildResult, + progress: &mut UnboundedSender, + ) -> Result { + _ = progress.start_send(UpdateBuildProgress { + stage: Stage::OptimizingAssets, + update: UpdateStage::Start, + }); + + // Start Manganis linker intercept. + let linker_args = vec![format!("{}", self.dioxus_crate.out_dir().display())]; + + manganis_cli_support::start_linker_intercept( + &LinkCommand::command_name(), + cargo_args, + Some(linker_args), + )?; + + let file_name = self.dioxus_crate.executable_name(); + + // Move the final output executable into the dist folder + let out_dir = self.dioxus_crate.out_dir(); + if !out_dir.is_dir() { + create_dir_all(&out_dir)?; + } + let mut output_path = out_dir.join(file_name); + if self.web { + output_path.set_extension("wasm"); + } else if cfg!(windows) { + output_path.set_extension("exe"); + } + if let Some(res_path) = &cargo_build_result.output_location { + std::fs::copy(res_path, &output_path)?; + } + + self.copy_assets_dir()?; + + let assets = if !self.build_arguments.skip_assets { + let assets = asset_manifest(&self.dioxus_crate); + // Collect assets + process_assets(&self.dioxus_crate, &assets, progress)?; + // Create the __assets_head.html file for bundling + create_assets_head(&self.dioxus_crate, &assets)?; + Some(assets) + } else { + None + }; + + // Create the build result + let build_result = BuildResult { + executable: output_path, + web: self.web, + platform: self + .build_arguments + .platform + .expect("To be resolved by now"), + }; + + // If this is a web build, run web post processing steps + if self.web { + self.post_process_web_build(&build_result, assets.as_ref(), progress) + .await?; + } + + Ok(build_result) + } + + pub fn copy_assets_dir(&self) -> anyhow::Result<()> { + tracing::info!("Copying public assets to the output directory..."); + let out_dir = self.dioxus_crate.out_dir(); + let asset_dir = self.dioxus_crate.asset_dir(); + + if asset_dir.is_dir() { + // Only pre-compress the assets from the web build. Desktop assets are not served, so they don't need to be pre_compressed + let pre_compress = self.web + && self + .dioxus_crate + .should_pre_compress_web_assets(self.build_arguments.release); + + copy_dir_to(asset_dir, out_dir, pre_compress)?; + } + Ok(()) + } +} diff --git a/packages/cli/src/builder/fullstack.rs b/packages/cli/src/builder/fullstack.rs new file mode 100644 index 000000000..141c6e26f --- /dev/null +++ b/packages/cli/src/builder/fullstack.rs @@ -0,0 +1,109 @@ +use crate::builder::Build; +use crate::dioxus_crate::DioxusCrate; +use dioxus_cli_config::Platform; + +use crate::builder::BuildRequest; +use std::path::PathBuf; + +static CLIENT_RUST_FLAGS: &[&str] = &["-Cdebuginfo=none", "-Cstrip=debuginfo"]; +// The `opt-level=2` increases build times, but can noticeably decrease time +// between saving changes and being able to interact with an app. The "overall" +// time difference (between having and not having the optimization) can be +// almost imperceptible (~1 s) but also can be very noticeable (~6 s) — depends +// on setup (hardware, OS, browser, idle load). +static SERVER_RUST_FLAGS: &[&str] = &["-O"]; +static DEBUG_RUST_FLAG: &str = "-Cdebug-assertions"; + +fn add_debug_rust_flags(build: &Build, flags: &mut Vec) { + if !build.release { + flags.push(DEBUG_RUST_FLAG.to_string()); + } +} + +fn fullstack_rust_flags(build: &Build, base_flags: &[&str]) -> Vec { + // If we are forcing debug mode, don't add any debug flags + if build.force_debug { + return Default::default(); + } + + let mut rust_flags = base_flags.iter().map(ToString::to_string).collect(); + add_debug_rust_flags(build, &mut rust_flags); + rust_flags +} + +// Fullstack builds run the server and client builds parallel by default +// To make them run in parallel, we need to set up different target directories for the server and client within /.dioxus +fn get_target_directory(build: &Build, target: PathBuf) -> Option { + (!build.force_sequential).then_some(target) +} + +impl BuildRequest { + pub(crate) fn new_fullstack( + config: DioxusCrate, + build_arguments: Build, + serve: bool, + ) -> Vec { + vec![ + Self::new_server(serve, &config, &build_arguments), + Self::new_client(serve, &config, &build_arguments), + ] + } + + fn new_with_target_directory_rust_flags_and_features( + serve: bool, + config: &DioxusCrate, + build: &Build, + target_directory: PathBuf, + rust_flags: &[&str], + feature: String, + web: bool, + ) -> Self { + let config = config.clone(); + let mut build = build.clone(); + build.platform = Some(if web { + Platform::Web + } else { + Platform::Desktop + }); + // Set the target directory we are building the server in + let target_dir = get_target_directory(&build, target_directory); + // Add the server feature to the features we pass to the build + build.target_args.features.push(feature); + + // Add the server flags to the build arguments + let rust_flags = fullstack_rust_flags(&build, rust_flags); + + Self { + web, + serve, + build_arguments: build.clone(), + dioxus_crate: config, + rust_flags, + target_dir, + } + } + + fn new_server(serve: bool, config: &DioxusCrate, build: &Build) -> Self { + Self::new_with_target_directory_rust_flags_and_features( + serve, + config, + build, + config.server_target_dir(), + SERVER_RUST_FLAGS, + build.target_args.server_feature.clone(), + false, + ) + } + + fn new_client(serve: bool, config: &DioxusCrate, build: &Build) -> Self { + Self::new_with_target_directory_rust_flags_and_features( + serve, + config, + build, + config.client_target_dir(), + CLIENT_RUST_FLAGS, + build.target_args.client_feature.clone(), + true, + ) + } +} diff --git a/packages/cli/src/builder/mod.rs b/packages/cli/src/builder/mod.rs new file mode 100644 index 000000000..21b588dd5 --- /dev/null +++ b/packages/cli/src/builder/mod.rs @@ -0,0 +1,145 @@ +use crate::build::Build; +use crate::cli::serve::ServeArguments; +use crate::dioxus_crate::DioxusCrate; +use crate::Result; +use dioxus_cli_config::{Platform, RuntimeCLIArguments}; +use futures_util::stream::select_all; +use futures_util::StreamExt; +use std::net::SocketAddr; +use std::{path::PathBuf, process::Stdio}; +use tokio::process::{Child, Command}; + +mod cargo; +mod fullstack; +mod prepare_html; +mod progress; +mod web; +pub use progress::{BuildMessage, MessageType, Stage, UpdateBuildProgress, UpdateStage}; + +/// A request for a project to be built +pub struct BuildRequest { + /// Whether the build is for serving the application + pub serve: bool, + /// Whether this is a web build + pub web: bool, + /// The configuration for the crate we are building + pub dioxus_crate: DioxusCrate, + /// The arguments for the build + pub build_arguments: Build, + /// The rustc flags to pass to the build + pub rust_flags: Vec, + /// The target directory for the build + pub target_dir: Option, +} + +impl BuildRequest { + pub fn create( + serve: bool, + dioxus_crate: &DioxusCrate, + build_arguments: impl Into, + ) -> Vec { + let build_arguments = build_arguments.into(); + let dioxus_crate = dioxus_crate.clone(); + let platform = build_arguments.platform(); + match platform { + Platform::Web | Platform::Desktop => { + let web = platform == Platform::Web; + vec![Self { + serve, + web, + dioxus_crate, + build_arguments, + rust_flags: Default::default(), + target_dir: Default::default(), + }] + } + Platform::StaticGeneration | Platform::Fullstack => { + Self::new_fullstack(dioxus_crate, build_arguments, serve) + } + _ => unimplemented!("Unknown platform: {platform:?}"), + } + } + + pub async fn build_all_parallel(build_requests: Vec) -> Result> { + let multi_platform_build = build_requests.len() > 1; + let mut build_progress = Vec::new(); + let mut set = tokio::task::JoinSet::new(); + for build_request in build_requests { + let (tx, rx) = futures_channel::mpsc::unbounded(); + build_progress.push((build_request.build_arguments.platform(), rx)); + set.spawn(async move { build_request.build(tx).await }); + } + + // Watch the build progress as it comes in + loop { + let mut next = select_all( + build_progress + .iter_mut() + .map(|(platform, rx)| rx.map(move |update| (*platform, update))), + ); + match next.next().await { + Some((platform, update)) => { + if multi_platform_build { + print!("{platform} build: "); + update.to_std_out(); + } else { + update.to_std_out(); + } + } + None => { + break; + } + } + } + + let mut all_results = Vec::new(); + + while let Some(result) = set.join_next().await { + let result = result + .map_err(|_| crate::Error::Unique("Failed to build project".to_owned()))??; + all_results.push(result); + } + + Ok(all_results) + } +} + +#[derive(Debug, Clone)] +pub(crate) struct BuildResult { + pub executable: PathBuf, + pub web: bool, + pub platform: Platform, +} + +impl BuildResult { + /// Open the executable if this is a native build + pub fn open( + &self, + serve: &ServeArguments, + fullstack_address: Option, + ) -> std::io::Result> { + if self.web { + return Ok(None); + } + + let arguments = RuntimeCLIArguments::new(serve.address.address(), fullstack_address); + let executable = self.executable.canonicalize()?; + // This is the /dist folder generally + let output_folder = executable.parent().unwrap(); + // This is the workspace folder + let workspace_folder = output_folder.parent().unwrap(); + Ok(Some( + Command::new(&executable) + // When building the fullstack server, we need to forward the serve arguments (like port) to the fullstack server through env vars + .env( + dioxus_cli_config::__private::SERVE_ENV, + serde_json::to_string(&arguments).unwrap(), + ) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .kill_on_drop(true) + .current_dir(workspace_folder) + .spawn()?, + )) + } +} diff --git a/packages/cli/src/builder/prepare_html.rs b/packages/cli/src/builder/prepare_html.rs new file mode 100644 index 000000000..39c894bc9 --- /dev/null +++ b/packages/cli/src/builder/prepare_html.rs @@ -0,0 +1,132 @@ +//! Build the HTML file to load a web application. The index.html file may be created from scratch or modified from the `index.html` file in the crate root. + +use super::BuildRequest; +use crate::Result; +use manganis_cli_support::AssetManifest; +use std::fmt::Write; +use std::path::Path; + +const DEFAULT_HTML: &str = include_str!("../../assets/index.html"); + +impl BuildRequest { + pub(crate) fn prepare_html(&self, assets: Option<&AssetManifest>) -> Result { + let mut html = html_or_default(&self.dioxus_crate.crate_dir()); + + // Inject any resources from the config into the html + self.inject_resources(&mut html, assets)?; + + // Inject loading scripts if they are not already present + self.inject_loading_scripts(&mut html); + + // Replace any special placeholders in the HTML with resolved values + self.replace_template_placeholders(&mut html); + + let title = self.dioxus_crate.dioxus_config.web.app.title.clone(); + + replace_or_insert_before("{app_title}", ") -> Result<()> { + // Collect all resources into a list of styles and scripts + let resources = &self.dioxus_crate.dioxus_config.web.resource; + let mut style_list = resources.style.clone().unwrap_or_default(); + let mut script_list = resources.script.clone().unwrap_or_default(); + + if self.serve { + style_list.extend(resources.dev.style.iter().cloned()); + script_list.extend(resources.dev.script.iter().cloned()); + } + + let mut head_resources = String::new(); + // Add all styles to the head + for style in style_list { + writeln!( + &mut head_resources, + "", + &style.to_str().unwrap(), + )?; + } + + // Add all scripts to the head + for script in script_list { + writeln!( + &mut head_resources, + "", + &script.to_str().unwrap(), + )?; + } + + // Inject any resources from manganis into the head + if let Some(assets) = assets { + head_resources.push_str(&assets.head()); + } + + replace_or_insert_before("{style_include}", " + // We can't use a module script here because we need to start the script immediately when streaming + import("/{base_path}/assets/dioxus/{app_name}.js").then( + ({ default: init }) => { + init("/{base_path}/assets/dioxus/{app_name}_bg.wasm").then((wasm) => { + if (wasm.__wbindgen_start == undefined) { + wasm.main(); + } + }); + } + ); + + + String { + let custom_html_file = crate_root.join("index.html"); + std::fs::read_to_string(custom_html_file).unwrap_or_else(|_| String::from(DEFAULT_HTML)) +} + +/// Replace a string or insert the new contents before a marker +fn replace_or_insert_before( + replace: &str, + or_insert_before: &str, + with: &str, + content: &mut String, +) { + if content.contains(replace) { + *content = content.replace(replace, with); + } else if let Some(pos) = content.find(or_insert_before) { + content.insert_str(pos, with); + } +} diff --git a/packages/cli/src/builder/progress.rs b/packages/cli/src/builder/progress.rs new file mode 100644 index 000000000..b7457b3aa --- /dev/null +++ b/packages/cli/src/builder/progress.rs @@ -0,0 +1,273 @@ +//! Report progress about the build to the user. We use channels to report progress back to the CLI. +use anyhow::Context; +use cargo_metadata::{diagnostic::Diagnostic, Message}; +use futures_channel::mpsc::UnboundedSender; +use serde::Deserialize; +use std::ops::Deref; +use std::path::PathBuf; +use std::process::Stdio; +use tokio::io::AsyncBufReadExt; +use tracing::Level; + +use super::BuildRequest; + +#[derive(Default, Debug, PartialOrd, Ord, PartialEq, Eq)] +pub enum Stage { + #[default] + Initializing = 0, + InstallingWasmTooling = 1, + Compiling = 2, + OptimizingWasm = 3, + OptimizingAssets = 4, + Finished = 5, +} + +impl Deref for Stage { + type Target = str; + + fn deref(&self) -> &Self::Target { + match self { + Stage::Initializing => "Initializing", + Stage::InstallingWasmTooling => "Installing Wasm Tooling", + Stage::Compiling => "Compiling", + Stage::OptimizingWasm => "Optimizing Wasm", + Stage::OptimizingAssets => "Optimizing Assets", + Stage::Finished => "Finished", + } + } +} + +impl std::fmt::Display for Stage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.deref()) + } +} + +pub struct UpdateBuildProgress { + pub stage: Stage, + pub update: UpdateStage, +} + +impl UpdateBuildProgress { + pub fn to_std_out(&self) { + match &self.update { + UpdateStage::Start => println!("--- {} ---", self.stage), + UpdateStage::AddMessage(message) => match &message.message { + MessageType::Cargo(message) => { + println!("{}", message.rendered.clone().unwrap_or_default()); + } + MessageType::Text(message) => { + println!("{}", message); + } + }, + UpdateStage::SetProgress(progress) => { + println!("Build progress {:0.0}%", progress * 100.0); + } + UpdateStage::Failed(message) => { + println!("Build failed: {}", message); + } + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum UpdateStage { + Start, + AddMessage(BuildMessage), + SetProgress(f64), + Failed(String), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct BuildMessage { + pub level: Level, + pub message: MessageType, + pub source: Option, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum MessageType { + Cargo(Diagnostic), + Text(String), +} + +impl From for BuildMessage { + fn from(message: Diagnostic) -> Self { + Self { + level: match message.level { + cargo_metadata::diagnostic::DiagnosticLevel::Ice + | cargo_metadata::diagnostic::DiagnosticLevel::FailureNote + | cargo_metadata::diagnostic::DiagnosticLevel::Error => Level::ERROR, + cargo_metadata::diagnostic::DiagnosticLevel::Warning => Level::WARN, + cargo_metadata::diagnostic::DiagnosticLevel::Note => Level::INFO, + cargo_metadata::diagnostic::DiagnosticLevel::Help => Level::DEBUG, + _ => Level::DEBUG, + }, + source: Some("cargo".to_string()), + message: MessageType::Cargo(message), + } + } +} + +pub(crate) async fn build_cargo( + crate_count: usize, + mut cmd: tokio::process::Command, + progress: &mut UnboundedSender, +) -> anyhow::Result { + _ = progress.start_send(UpdateBuildProgress { + stage: Stage::Compiling, + update: UpdateStage::Start, + }); + + let mut child = cmd + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .context("Failed to spawn cargo build")?; + let stdout = child.stdout.take().unwrap(); + let stderr = child.stderr.take().unwrap(); + let stdout = tokio::io::BufReader::new(stdout); + let stderr = tokio::io::BufReader::new(stderr); + let mut output_location = None; + + let mut stdout = stdout.lines(); + let mut stderr = stderr.lines(); + let mut units_compiled = 0; + let mut errors = Vec::new(); + loop { + let line = tokio::select! { + line = stdout.next_line() => { + line + } + line = stderr.next_line() => { + line + } + }; + let Some(line) = line? else { + break; + }; + let mut deserializer = serde_json::Deserializer::from_str(line.trim()); + deserializer.disable_recursion_limit(); + + let message = Message::deserialize(&mut deserializer).unwrap_or(Message::TextLine(line)); + match message { + Message::CompilerMessage(msg) => { + let message = msg.message; + _ = progress.start_send(UpdateBuildProgress { + stage: Stage::Compiling, + update: UpdateStage::AddMessage(message.clone().into()), + }); + const WARNING_LEVELS: &[cargo_metadata::diagnostic::DiagnosticLevel] = &[ + cargo_metadata::diagnostic::DiagnosticLevel::Help, + cargo_metadata::diagnostic::DiagnosticLevel::Note, + cargo_metadata::diagnostic::DiagnosticLevel::Warning, + cargo_metadata::diagnostic::DiagnosticLevel::Error, + cargo_metadata::diagnostic::DiagnosticLevel::FailureNote, + cargo_metadata::diagnostic::DiagnosticLevel::Ice, + ]; + const FATAL_LEVELS: &[cargo_metadata::diagnostic::DiagnosticLevel] = &[ + cargo_metadata::diagnostic::DiagnosticLevel::Error, + cargo_metadata::diagnostic::DiagnosticLevel::FailureNote, + cargo_metadata::diagnostic::DiagnosticLevel::Ice, + ]; + if WARNING_LEVELS.contains(&message.level) { + if let Some(rendered) = message.rendered { + errors.push(rendered); + } + } + if FATAL_LEVELS.contains(&message.level) { + return Err(anyhow::anyhow!(errors.join("\n"))); + } + } + Message::CompilerArtifact(artifact) => { + units_compiled += 1; + if let Some(executable) = artifact.executable { + output_location = Some(executable.into()); + } else { + let build_progress = units_compiled as f64 / crate_count as f64; + _ = progress.start_send(UpdateBuildProgress { + stage: Stage::Compiling, + update: UpdateStage::SetProgress((build_progress).clamp(0.0, 1.00)), + }); + } + } + Message::BuildScriptExecuted(_) => { + units_compiled += 1; + } + Message::BuildFinished(finished) => { + if !finished.success { + return Err(anyhow::anyhow!("Build failed")); + } + } + Message::TextLine(line) => { + _ = progress.start_send(UpdateBuildProgress { + stage: Stage::Compiling, + update: UpdateStage::AddMessage(BuildMessage { + level: Level::DEBUG, + message: MessageType::Text(line), + source: None, + }), + }); + } + _ => { + // Unknown message + } + } + } + + Ok(CargoBuildResult { output_location }) +} + +pub(crate) struct CargoBuildResult { + pub(crate) output_location: Option, +} + +impl BuildRequest { + /// Try to get the unit graph for the crate. This is a nightly only feature which may not be available with the current version of rustc the user has installed. + async fn get_unit_count(&self) -> Option { + #[derive(Debug, Deserialize)] + struct UnitGraph { + units: Vec, + } + + let mut cmd = tokio::process::Command::new("cargo"); + cmd.arg("+nightly"); + cmd.arg("build"); + cmd.arg("--unit-graph"); + cmd.arg("-Z").arg("unstable-options"); + + cmd.args(self.build_arguments()); + + let output = cmd + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .ok()?; + if !output.status.success() { + return None; + } + + let output_text = String::from_utf8(output.stdout).ok()?; + let graph: UnitGraph = serde_json::from_str(&output_text).ok()?; + + Some(graph.units.len()) + } + + /// Get an estimate of the number of units in the crate. If nightly rustc is not available, this will return an estimate of the number of units in the crate based on cargo metadata. + /// TODO: always use https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#unit-graph once it is stable + pub(crate) async fn get_unit_count_estimate(&self) -> usize { + // Try to get it from nightly + self.get_unit_count().await.unwrap_or_else(|| { + // Otherwise, use cargo metadata + (self + .dioxus_crate + .krates + .krates_filtered(krates::DepKind::Dev) + .iter() + .map(|k| k.targets.len()) + .sum::() as f64 + / 3.5) as usize + }) + } +} diff --git a/packages/cli/src/builder/web.rs b/packages/cli/src/builder/web.rs new file mode 100644 index 000000000..3524d727e --- /dev/null +++ b/packages/cli/src/builder/web.rs @@ -0,0 +1,182 @@ +use super::BuildRequest; +use super::BuildResult; +use crate::assets::pre_compress_folder; +use crate::builder::progress::Stage; +use crate::builder::progress::UpdateBuildProgress; +use crate::builder::progress::UpdateStage; +use crate::error::{Error, Result}; +use dioxus_cli_config::WasmOptLevel; +use futures_channel::mpsc::UnboundedSender; +use manganis_cli_support::AssetManifest; +use std::path::Path; +use tokio::process::Command; +use wasm_bindgen_cli_support::Bindgen; + +// Attempt to automatically recover from a bindgen failure by updating the wasm-bindgen version +async fn update_wasm_bindgen_version() -> Result<()> { + let cli_bindgen_version = wasm_bindgen_shared::version(); + tracing::info!("Attempting to recover from bindgen failure by setting the wasm-bindgen version to {cli_bindgen_version}..."); + + let output = Command::new("cargo") + .args([ + "update", + "-p", + "wasm-bindgen", + "--precise", + &cli_bindgen_version, + ]) + .output() + .await; + let mut error_message = None; + if let Ok(output) = output { + if output.status.success() { + tracing::info!("Successfully updated wasm-bindgen to {cli_bindgen_version}"); + return Ok(()); + } else { + error_message = Some(output); + } + } + + if let Some(output) = error_message { + tracing::error!("Failed to update wasm-bindgen: {:#?}", output); + } + + Err(Error::BuildFailed(format!("WASM bindgen build failed!\nThis is probably due to the Bindgen version, dioxus-cli is using `{cli_bindgen_version}` which is not compatible with your crate.\nPlease reinstall the dioxus cli to fix this issue.\nYou can reinstall the dioxus cli by running `cargo install dioxus-cli --force` and then rebuild your project"))) +} + +/// Check if the wasm32-unknown-unknown target is installed and try to install it if not +pub(crate) async fn install_web_build_tooling( + progress: &mut UnboundedSender, +) -> Result<()> { + // If the user has rustup, we can check if the wasm32-unknown-unknown target is installed + // Otherwise we can just assume it is installed - which is not great... + // Eventually we can poke at the errors and let the user know they need to install the target + if let Ok(wasm_check_command) = Command::new("rustup").args(["show"]).output().await { + let wasm_check_output = String::from_utf8(wasm_check_command.stdout).unwrap(); + if !wasm_check_output.contains("wasm32-unknown-unknown") { + _ = progress.start_send(UpdateBuildProgress { + stage: Stage::InstallingWasmTooling, + update: UpdateStage::Start, + }); + tracing::info!("wasm32-unknown-unknown target not detected, installing.."); + let _ = Command::new("rustup") + .args(["target", "add", "wasm32-unknown-unknown"]) + .output() + .await?; + } + } + + Ok(()) +} + +impl BuildRequest { + async fn run_wasm_bindgen(&self, input_path: &Path, bindgen_outdir: &Path) -> Result<()> { + tracing::info!("Running wasm-bindgen"); + let input_path = input_path.to_path_buf(); + let bindgen_outdir = bindgen_outdir.to_path_buf(); + let keep_debug = + self.dioxus_crate.dioxus_config.web.wasm_opt.debug || (!self.build_arguments.release); + let name = self.dioxus_crate.dioxus_config.application.name.clone(); + let run_wasm_bindgen = move || { + // [3] Bindgen the final binary for use easy linking + let mut bindgen_builder = Bindgen::new(); + + bindgen_builder + .input_path(&input_path) + .web(true) + .unwrap() + .debug(keep_debug) + .demangle(keep_debug) + .keep_debug(keep_debug) + .reference_types(true) + .remove_name_section(!keep_debug) + .remove_producers_section(!keep_debug) + .out_name(&name) + .generate(&bindgen_outdir) + .unwrap(); + }; + let bindgen_result = tokio::task::spawn_blocking(run_wasm_bindgen.clone()).await; + + // WASM bindgen requires the exact version of the bindgen schema to match the version the CLI was built with + // If we get an error, we can try to recover by pinning the user's wasm-bindgen version to the version we used + if let Err(err) = bindgen_result { + tracing::error!("Bindgen build failed: {:?}", err); + update_wasm_bindgen_version().await?; + run_wasm_bindgen(); + } + + Ok(()) + } + + /// Post process the WASM build artifacts + pub(crate) async fn post_process_web_build( + &self, + build_result: &BuildResult, + assets: Option<&AssetManifest>, + progress: &mut UnboundedSender, + ) -> Result<()> { + _ = progress.start_send(UpdateBuildProgress { + stage: Stage::OptimizingWasm, + update: UpdateStage::Start, + }); + + // Create the index.html file + let html = self.prepare_html(assets)?; + let html_path = self.dioxus_crate.out_dir().join("index.html"); + std::fs::write(&html_path, html)?; + + // Find the wasm file + let output_location = build_result.executable.clone(); + let input_path = output_location.with_extension("wasm"); + + // Create the directory where the bindgen output will be placed + let bindgen_outdir = self.dioxus_crate.out_dir().join("assets").join("dioxus"); + + // Run wasm-bindgen + self.run_wasm_bindgen(&input_path, &bindgen_outdir).await?; + + // Run wasm-opt if this is a release build + if self.build_arguments.release { + tracing::info!("Running optimization with wasm-opt..."); + let mut options = match self.dioxus_crate.dioxus_config.web.wasm_opt.level { + WasmOptLevel::Z => { + wasm_opt::OptimizationOptions::new_optimize_for_size_aggressively() + } + WasmOptLevel::S => wasm_opt::OptimizationOptions::new_optimize_for_size(), + WasmOptLevel::Zero => wasm_opt::OptimizationOptions::new_opt_level_0(), + WasmOptLevel::One => wasm_opt::OptimizationOptions::new_opt_level_1(), + WasmOptLevel::Two => wasm_opt::OptimizationOptions::new_opt_level_2(), + WasmOptLevel::Three => wasm_opt::OptimizationOptions::new_opt_level_3(), + WasmOptLevel::Four => wasm_opt::OptimizationOptions::new_opt_level_4(), + }; + let wasm_file = bindgen_outdir.join(format!( + "{}_bg.wasm", + self.dioxus_crate.dioxus_config.application.name + )); + let old_size = wasm_file.metadata()?.len(); + options + // WASM bindgen relies on reference types + .enable_feature(wasm_opt::Feature::ReferenceTypes) + .debug_info(self.dioxus_crate.dioxus_config.web.wasm_opt.debug) + .run(&wasm_file, &wasm_file) + .map_err(|err| Error::Other(anyhow::anyhow!(err)))?; + let new_size = wasm_file.metadata()?.len(); + tracing::info!( + "wasm-opt reduced WASM size from {} to {} ({:2}%)", + old_size, + new_size, + (new_size as f64 - old_size as f64) / old_size as f64 * 100.0 + ); + } + + // If pre-compressing is enabled, we can pre_compress the wasm-bindgen output + let pre_compress = self + .dioxus_crate + .should_pre_compress_web_assets(self.build_arguments.release); + tokio::task::spawn_blocking(move || pre_compress_folder(&bindgen_outdir, pre_compress)) + .await + .unwrap()?; + + Ok(()) + } +} diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index 3cc7b4804..f77e7e946 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -1,121 +1,145 @@ -use crate::{assets::AssetConfigDropGuard, server::fullstack}; +use anyhow::Context; use dioxus_cli_config::Platform; +use crate::{builder::BuildRequest, dioxus_crate::DioxusCrate}; + use super::*; -/// Build the Rust WASM app and all of its assets. -#[derive(Clone, Debug, Parser)] +/// Information about the target to build +#[derive(Clone, Debug, Default, Deserialize, Parser)] +pub struct TargetArgs { + /// Build for nightly [default: false] + #[clap(long)] + pub nightly: bool, + + /// Build a example [default: ""] + #[clap(long)] + pub example: Option, + + /// Build a binary [default: ""] + #[clap(long)] + pub bin: Option, + + /// The package to build + #[clap(long)] + pub package: Option, + + /// Space separated list of features to activate + #[clap(long)] + pub features: Vec, + + /// The feature to use for the client in a fullstack app [default: "web"] + #[clap(long, default_value_t = { "web".to_string() })] + pub client_feature: String, + + /// The feature to use for the server in a fullstack app [default: "server"] + #[clap(long, default_value_t = { "server".to_string() })] + pub server_feature: String, + + /// Rustc platform triple + #[clap(long)] + pub target: Option, +} + +/// Build the Rust Dioxus app and all of its assets. +#[derive(Clone, Debug, Default, Deserialize, Parser)] #[clap(name = "build")] pub struct Build { + /// Build in release mode [default: false] + #[clap(long, short)] + #[serde(default)] + pub release: bool, + + /// This flag only applies to fullstack builds. By default fullstack builds will run with something in between debug and release mode. This flag will force the build to run in debug mode. [default: false] + #[clap(long)] + #[serde(default)] + pub force_debug: bool, + + /// This flag only applies to fullstack builds. By default fullstack builds will run the server and client builds in parallel. This flag will force the build to run the server build first, then the client build. [default: false] + #[clap(long)] + #[serde(default)] + pub force_sequential: bool, + + // Use verbose output [default: false] + #[clap(long)] + #[serde(default)] + pub verbose: bool, + + /// Build with custom profile + #[clap(long)] + pub profile: Option, + + /// Build platform: support Web & Desktop [default: "default_platform"] + #[clap(long, value_enum)] + pub platform: Option, + + /// Skip collecting assets from dependencies [default: false] + #[clap(long)] + #[serde(default)] + pub skip_assets: bool, + + /// Extra arguments passed to cargo build + #[clap(last = true)] + pub cargo_args: Vec, + + /// Inject scripts to load the wasm and js files for your dioxus app if they are not already present [default: true] + #[clap(long, default_value_t = true)] + pub inject_loading_scripts: bool, + + /// Information about the target to build #[clap(flatten)] - pub build: ConfigOptsBuild, + pub target_args: TargetArgs, } impl Build { - /// Note: `rust_flags` argument is only used for the fullstack platform. - pub fn build( - self, - bin: Option, - target_dir: Option<&std::path::Path>, - rust_flags: Option, - ) -> Result<()> { - let mut crate_config = dioxus_cli_config::CrateConfig::new(bin)?; - if let Some(target_dir) = target_dir { - crate_config.target_dir = target_dir.to_path_buf(); - } - - // change the release state. - crate_config.with_release(self.build.release); - crate_config.with_verbose(self.build.verbose); - - if self.build.example.is_some() { - crate_config.as_example(self.build.example.clone().unwrap()); - } - - if self.build.profile.is_some() { - crate_config.set_profile(self.build.profile.clone().unwrap()); - } - - if self.build.features.is_some() { - crate_config.set_features(self.build.features.clone().unwrap()); - } - + pub fn resolve(&mut self, dioxus_crate: &mut DioxusCrate) -> Result<()> { + // Inherit the platform from the defaults let platform = self - .build .platform - .unwrap_or(crate_config.dioxus_config.application.default_platform); + .unwrap_or_else(|| self.auto_detect_platform(dioxus_crate)); + self.platform = Some(platform); - if let Some(target) = self.build.target.clone() { - crate_config.set_target(target); - } - - crate_config.set_cargo_args(self.build.cargo_args.clone()); - crate_config.extend_with_platform(platform); - - // #[cfg(feature = "plugin")] - // let _ = crate::plugin::PluginManager::on_build_start(&crate_config, &platform); - - let build_result = match platform { - Platform::Web => { - // `rust_flags` are used by fullstack's client build. - crate::builder::build_web(&crate_config, self.build.skip_assets, rust_flags)? - } - Platform::Desktop => { - // Since desktop platform doesn't use `rust_flags`, this - // argument is explicitly set to `None`. - crate::builder::build_desktop(&crate_config, false, self.build.skip_assets, None)? - } - Platform::Fullstack | Platform::StaticGeneration => { - // Fullstack mode must be built with web configs on the desktop - // (server) binary as well as the web binary - let _config = AssetConfigDropGuard::new(); - let client_rust_flags = fullstack::client_rust_flags(&self.build); - let server_rust_flags = fullstack::server_rust_flags(&self.build); - { - let mut web_config = crate_config.clone(); - let web_feature = self.build.client_feature; - let features = &mut web_config.features; - match features { - Some(features) => { - features.push(web_feature); - } - None => web_config.features = Some(vec![web_feature]), - }; - crate::builder::build_web( - &web_config, - self.build.skip_assets, - Some(client_rust_flags), - )?; - } - { - let mut desktop_config = crate_config.clone(); - let desktop_feature = self.build.server_feature; - let features = &mut desktop_config.features; - match features { - Some(features) => { - features.push(desktop_feature); - } - None => desktop_config.features = Some(vec![desktop_feature]), - }; - crate::builder::build_desktop( - &desktop_config, - false, - self.build.skip_assets, - Some(server_rust_flags), - )? - } - } - _ => unreachable!(), - }; - - let temp = gen_page(&crate_config, build_result.assets.as_ref(), false); - - let mut file = std::fs::File::create(crate_config.out_dir().join("index.html"))?; - file.write_all(temp.as_bytes())?; - - // #[cfg(feature = "plugin")] - // let _ = crate::plugin::PluginManager::on_build_finish(&crate_config, &platform); + // Add any features required to turn on the platform we are building for + self.target_args + .features + .extend(dioxus_crate.features_for_platform(platform)); Ok(()) } + + pub async fn build(&mut self, dioxus_crate: &mut DioxusCrate) -> Result<()> { + self.resolve(dioxus_crate)?; + let build_requests = BuildRequest::create(false, dioxus_crate, self.clone()); + BuildRequest::build_all_parallel(build_requests).await?; + Ok(()) + } + + pub async fn run(&mut self) -> anyhow::Result<()> { + let mut dioxus_crate = + DioxusCrate::new(&self.target_args).context("Failed to load Dioxus workspace")?; + self.build(&mut dioxus_crate).await?; + Ok(()) + } + + fn auto_detect_platform(&self, resolved: &DioxusCrate) -> Platform { + for dioxus in resolved.krates.krates_by_name("dioxus") { + let Some(features) = resolved.krates.get_enabled_features(dioxus.kid) else { + continue; + }; + + if let Some(platform) = features + .iter() + .find_map(|platform| platform.parse::().ok()) + { + return platform; + } + } + + resolved.dioxus_config.application.default_platform + } + + /// Get the platform from the build arguments + pub fn platform(&self) -> Platform { + self.platform.unwrap_or_default() + } } diff --git a/packages/cli/src/cli/bundle.rs b/packages/cli/src/cli/bundle.rs index e69eae010..f82843edc 100644 --- a/packages/cli/src/cli/bundle.rs +++ b/packages/cli/src/cli/bundle.rs @@ -1,11 +1,13 @@ -use core::panic; -use dioxus_cli_config::ExecutableType; -use std::{env::current_dir, fs::create_dir_all, str::FromStr}; - +use crate::build::Build; +use crate::DioxusCrate; +use anyhow::Context; +use std::env::current_dir; +use std::fs::create_dir_all; +use std::ops::Deref; +use std::str::FromStr; use tauri_bundler::{BundleSettings, PackageSettings, SettingsBuilder}; use super::*; -use crate::{build_desktop, cfg::ConfigOptsBundle}; /// Bundle the Rust desktop app and all of its assets #[derive(Clone, Debug, Parser)] @@ -13,8 +15,17 @@ use crate::{build_desktop, cfg::ConfigOptsBundle}; pub struct Bundle { #[clap(long)] pub package: Option>, + /// The arguments for the dioxus build #[clap(flatten)] - pub build: ConfigOptsBundle, + pub build_arguments: Build, +} + +impl Deref for Bundle { + type Target = Build; + + fn deref(&self) -> &Self::Target { + &self.build_arguments + } } #[derive(Clone, Debug)] @@ -62,48 +73,19 @@ impl From for tauri_bundler::PackageType { } impl Bundle { - pub fn bundle(self, bin: Option) -> Result<()> { - let mut crate_config = dioxus_cli_config::CrateConfig::new(bin)?; + pub async fn bundle(mut self) -> anyhow::Result<()> { + let mut dioxus_crate = DioxusCrate::new(&self.build_arguments.target_args) + .context("Failed to load Dioxus workspace")?; - // change the release state. - crate_config.with_release(true); - crate_config.with_verbose(self.build.verbose); + self.build_arguments.resolve(&mut dioxus_crate)?; - if self.build.example.is_some() { - crate_config.as_example(self.build.example.unwrap()); - } - - if self.build.profile.is_some() { - crate_config.set_profile(self.build.profile.unwrap()); - } - - if let Some(target) = &self.build.target { - crate_config.set_target(target.to_string()); - } - - crate_config.set_cargo_args(self.build.cargo_args); - if let Some(platform) = self.build.platform { - crate_config.extend_with_platform(platform); - } - - if let Some(features) = self.build.features { - crate_config.set_features(features); - } - - // build the desktop app - // Since the `bundle()` function is only run for the desktop platform, - // the `rust_flags` argument is set to `None`. - build_desktop(&crate_config, false, false, None)?; + // Build the app + self.build_arguments.build(&mut dioxus_crate).await?; // copy the binary to the out dir - let package = crate_config.manifest.package.as_ref().unwrap(); + let package = dioxus_crate.package(); - let mut name: PathBuf = match &crate_config.executable { - ExecutableType::Binary(name) - | ExecutableType::Lib(name) - | ExecutableType::Example(name) => name, - } - .into(); + let mut name: PathBuf = dioxus_crate.executable_name().into(); if cfg!(windows) { name.set_extension("exe"); } @@ -111,12 +93,12 @@ impl Bundle { // bundle the app let binaries = vec![ tauri_bundler::BundleBinary::new(name.display().to_string(), true) - .set_src_path(Some(crate_config.crate_dir.display().to_string())), + .set_src_path(Some(dioxus_crate.workspace_dir().display().to_string())), ]; - let mut bundle_settings: BundleSettings = crate_config.dioxus_config.bundle.clone().into(); + let mut bundle_settings: BundleSettings = dioxus_crate.dioxus_config.bundle.clone().into(); if cfg!(windows) { - let windows_icon_override = crate_config + let windows_icon_override = dioxus_crate .dioxus_config .bundle .windows @@ -135,7 +117,7 @@ impl Bundle { if !path.exists() { create_dir_all(path.parent().unwrap()).unwrap(); let mut file = File::create(&path).unwrap(); - file.write_all(include_bytes!("../assets/icon.ico")) + file.write_all(include_bytes!("../../assets/icon.ico")) .unwrap(); } path @@ -145,10 +127,10 @@ impl Bundle { } // Copy the assets in the dist directory to the bundle - let static_asset_output_dir = &crate_config.dioxus_config.application.out_dir; + let static_asset_output_dir = &dioxus_crate.dioxus_config.application.out_dir; // Make sure the dist directory is relative to the crate directory let static_asset_output_dir = static_asset_output_dir - .strip_prefix(&crate_config.crate_dir) + .strip_prefix(dioxus_crate.workspace_dir()) .unwrap_or(static_asset_output_dir); let static_asset_output_dir = static_asset_output_dir.display().to_string(); @@ -156,8 +138,8 @@ impl Bundle { // Don't copy the executable or the old bundle directory let ignored_files = [ - crate_config.out_dir().join("bundle"), - crate_config.out_dir().join(name), + dioxus_crate.out_dir().join("bundle"), + dioxus_crate.out_dir().join(name), ]; for entry in std::fs::read_dir(&static_asset_output_dir)?.flatten() { @@ -181,27 +163,27 @@ impl Bundle { } let mut settings = SettingsBuilder::new() - .project_out_directory(crate_config.out_dir()) + .project_out_directory(dioxus_crate.out_dir()) .package_settings(PackageSettings { - product_name: crate_config.dioxus_config.application.name.clone(), - version: package.version().to_string(), - description: package.description().unwrap_or_default().to_string(), - homepage: Some(package.homepage().unwrap_or_default().to_string()), - authors: Some(Vec::from(package.authors())), - default_run: Some(crate_config.dioxus_config.application.name.clone()), + product_name: dioxus_crate.dioxus_config.application.name.clone(), + version: package.version.to_string(), + description: package.description.clone().unwrap_or_default(), + homepage: Some(package.homepage.clone().unwrap_or_default()), + authors: Some(package.authors.clone()), + default_run: Some(dioxus_crate.dioxus_config.application.name.clone()), }) .binaries(binaries) .bundle_settings(bundle_settings); - if let Some(packages) = self.package { + if let Some(packages) = &self.package { settings = settings.package_types( packages - .into_iter() + .iter() .map(|p| p.parse::().unwrap().into()) .collect(), ); } - if let Some(target) = &self.build.target { + if let Some(target) = &self.target_args.target { settings = settings.target(target.to_string()); } diff --git a/packages/cli/src/cli/cfg.rs b/packages/cli/src/cli/cfg.rs deleted file mode 100644 index 48a47b823..000000000 --- a/packages/cli/src/cli/cfg.rs +++ /dev/null @@ -1,218 +0,0 @@ -use dioxus_cli_config::Platform; -use dioxus_cli_config::ServeArguments; - -use super::*; - -/// Config options for the build system. -#[derive(Clone, Debug, Default, Deserialize, Parser)] -pub struct ConfigOptsBuild { - /// Build in release mode [default: false] - #[clap(long, short)] - #[serde(default)] - pub release: bool, - - /// This flag only applies to fullstack builds. By default fullstack builds will run with something in between debug and release mode. This flag will force the build to run in debug mode. [default: false] - #[clap(long)] - #[serde(default)] - pub force_debug: bool, - - /// This flag only applies to fullstack builds. By default fullstack builds will run the server and client builds in parallel. This flag will force the build to run the server build first, then the client build. [default: false] - #[clap(long)] - #[serde(default)] - pub force_sequential: bool, - - // Use verbose output [default: false] - #[clap(long)] - #[serde(default)] - pub verbose: bool, - - /// Build a example [default: ""] - #[clap(long)] - pub example: Option, - - /// Build with custom profile - #[clap(long)] - pub profile: Option, - - /// Build platform: support Web & Desktop [default: "default_platform"] - #[clap(long, value_enum)] - pub platform: Option, - - /// Skip collecting assets from dependencies [default: false] - #[clap(long)] - #[serde(default)] - pub skip_assets: bool, - - /// Space separated list of features to activate - #[clap(long)] - pub features: Option>, - - /// The feature to use for the client in a fullstack app [default: "web"] - #[clap(long, default_value_t = { "web".to_string() })] - pub client_feature: String, - - /// The feature to use for the server in a fullstack app [default: "server"] - #[clap(long, default_value_t = { "server".to_string() })] - pub server_feature: String, - - /// Rustc platform triple - #[clap(long)] - pub target: Option, - - /// Extra arguments passed to cargo build - #[clap(last = true)] - pub cargo_args: Vec, -} - -impl From for ConfigOptsBuild { - fn from(serve: ConfigOptsServe) -> Self { - Self { - target: serve.target, - release: serve.release, - verbose: serve.verbose, - example: serve.example, - profile: serve.profile, - platform: serve.platform, - features: serve.features, - client_feature: serve.client_feature, - server_feature: serve.server_feature, - skip_assets: serve.skip_assets, - force_debug: serve.force_debug, - force_sequential: serve.force_sequential, - cargo_args: serve.cargo_args, - } - } -} - -#[derive(Clone, Debug, Default, Deserialize, Parser)] -#[command(group = clap::ArgGroup::new("release-incompatible").multiple(true).conflicts_with("release"))] -pub struct ConfigOptsServe { - /// Arguments for the serve command - #[clap(flatten)] - pub(crate) server_arguments: ServeArguments, - - // TODO: Somehow make this default to `true` if the flag was provided. e.g. `dx serve --open` - // Currently it requires a value: `dx serve --open true` - /// Open the app in the default browser [default: false - unless project or global settings are set] - #[clap(long)] - pub open: Option, - - // TODO: See `open` field - /// Enable full hot reloading for the app [default: true - unless project or global settings are set] - #[clap(long, group = "release-incompatible")] - pub hot_reload: Option, - - /// Build a example [default: ""] - #[clap(long)] - pub example: Option, - - /// Build in release mode [default: false] - #[clap(long)] - #[serde(default)] - pub release: bool, - - /// This flag only applies to fullstack builds. By default fullstack builds will run with something in between debug and release mode. This flag will force the build to run in debug mode. [default: false] - #[clap(long)] - #[serde(default)] - pub force_debug: bool, - - /// This flag only applies to fullstack builds. By default fullstack builds will run the server and client builds in parallel. This flag will force the build to run the server build first, then the client build. [default: false] - #[clap(long)] - #[serde(default)] - pub force_sequential: bool, - - // Use verbose output [default: false] - #[clap(long)] - #[serde(default)] - pub verbose: bool, - - /// Build with custom profile - #[clap(long)] - pub profile: Option, - - /// Build platform: support Web, Desktop, and Fullstack [default: "default_platform"] - #[clap(long, value_enum)] - pub platform: Option, - - /// Build with hot reloading rsx. Will not work with release builds. [default: true] - #[clap(long)] - #[clap(default_missing_value("true"), - default_value("true"), - num_args(0..=1), - require_equals(true), - action = clap::ArgAction::Set, - )] - - /// Set cross-origin-policy to same-origin [default: false] - #[clap(name = "cross-origin-policy")] - #[clap(long)] - #[serde(default)] - pub cross_origin_policy: bool, - - /// Space separated list of features to activate - #[clap(long)] - pub features: Option>, - - /// Skip collecting assets from dependencies [default: false] - #[clap(long)] - #[serde(default)] - pub skip_assets: bool, - - /// The feature to use for the client in a fullstack app [default: "web"] - #[clap(long, default_value_t = { "web".to_string() })] - pub client_feature: String, - - /// The feature to use for the server in a fullstack app [default: "server"] - #[clap(long, default_value_t = { "server".to_string() })] - pub server_feature: String, - - /// Rustc platform triple - #[clap(long)] - pub target: Option, - - /// Additional arguments to pass to the executable - #[clap(long)] - pub args: Vec, - - /// Extra arguments passed to cargo build - #[clap(last = true)] - pub cargo_args: Vec, -} - -/// Config options for the bundling system. -#[derive(Clone, Debug, Default, Deserialize, Parser)] -pub struct ConfigOptsBundle { - /// Build in release mode [default: false] - #[clap(long, short)] - #[serde(default)] - pub release: bool, - - // Use verbose output [default: false] - #[clap(long)] - #[serde(default)] - pub verbose: bool, - - /// Build a example [default: ""] - #[clap(long)] - pub example: Option, - - /// Build with custom profile - #[clap(long)] - pub profile: Option, - - /// Build platform: support Web & Desktop [default: "default_platform"] - #[clap(long)] - pub platform: Option, - - /// Space separated list of features to activate - #[clap(long)] - pub features: Option>, - - /// Rustc platform triple - #[clap(long)] - pub target: Option, - - /// Extra arguments passed to cargo build - #[clap(last = true)] - pub cargo_args: Vec, -} diff --git a/packages/cli/src/cli/check.rs b/packages/cli/src/cli/check.rs index f16a2c81a..84374184d 100644 --- a/packages/cli/src/cli/check.rs +++ b/packages/cli/src/cli/check.rs @@ -1,6 +1,9 @@ +use crate::build::TargetArgs; use futures_util::{stream::FuturesUnordered, StreamExt}; use std::{path::Path, process::exit}; +use crate::DioxusCrate; + use super::*; // For reference, the rustfmt main.rs file @@ -12,30 +15,31 @@ pub struct Check { /// Input file #[clap(short, long)] pub file: Option, + + /// Information about the target to check + #[clap(flatten)] + pub target_args: TargetArgs, } impl Check { // Todo: check the entire crate - pub fn check(self) -> Result<()> { - let rt = tokio::runtime::Runtime::new().unwrap(); - - rt.block_on(async move { - match self.file { - // Default to checking the project - None => { - if let Err(e) = check_project_and_report().await { - eprintln!("error checking project: {}", e); - exit(1); - } - } - Some(file) => { - if let Err(e) = check_file_and_report(file).await { - eprintln!("failed to check file: {}", e); - exit(1); - } + pub async fn check(self) -> Result<()> { + match self.file { + // Default to checking the project + None => { + let dioxus_crate = DioxusCrate::new(&self.target_args)?; + if let Err(e) = check_project_and_report(dioxus_crate).await { + eprintln!("error checking project: {}", e); + exit(1); } } - }); + Some(file) => { + if let Err(e) = check_file_and_report(file).await { + eprintln!("failed to check file: {}", e); + exit(1); + } + } + } Ok(()) } @@ -50,11 +54,9 @@ async fn check_file_and_report(path: PathBuf) -> Result<()> { /// Runs using Tokio for multithreading, so it should be really really fast /// /// Doesn't do mod-descending, so it will still try to check unreachable files. TODO. -async fn check_project_and_report() -> Result<()> { - let crate_config = dioxus_cli_config::CrateConfig::new(None)?; - - let mut files_to_check = vec![]; - collect_rs_files(&crate_config.crate_dir, &mut files_to_check); +async fn check_project_and_report(dioxus_crate: DioxusCrate) -> Result<()> { + let mut files_to_check = vec![dioxus_crate.main_source_file()]; + collect_rs_files(&dioxus_crate.crate_dir(), &mut files_to_check); check_files_and_report(files_to_check).await } diff --git a/packages/cli/src/cli/clean.rs b/packages/cli/src/cli/clean.rs index a66bac3ac..cf8f9c652 100644 --- a/packages/cli/src/cli/clean.rs +++ b/packages/cli/src/cli/clean.rs @@ -1,3 +1,7 @@ +use crate::DioxusCrate; +use anyhow::Context; +use build::TargetArgs; + use super::*; /// Clean build artifacts. @@ -6,8 +10,9 @@ use super::*; pub struct Clean {} impl Clean { - pub fn clean(self, bin: Option) -> Result<()> { - let crate_config = dioxus_cli_config::CrateConfig::new(bin)?; + pub fn clean(self) -> anyhow::Result<()> { + let dioxus_crate = + DioxusCrate::new(&TargetArgs::default()).context("Failed to load Dioxus workspace")?; let output = Command::new("cargo") .arg("clean") @@ -16,15 +21,15 @@ impl Clean { .output()?; if !output.status.success() { - return custom_error!("Cargo clean failed."); + return Err(anyhow::anyhow!("Cargo clean failed.")); } - let out_dir = &crate_config.out_dir(); + let out_dir = &dioxus_crate.out_dir(); if out_dir.is_dir() { remove_dir_all(out_dir)?; } - let fullstack_out_dir = crate_config.fullstack_out_dir(); + let fullstack_out_dir = dioxus_crate.fullstack_out_dir(); if fullstack_out_dir.is_dir() { remove_dir_all(fullstack_out_dir)?; diff --git a/packages/cli/src/cli/config.rs b/packages/cli/src/cli/config.rs index e53e6519b..87a0d8a4b 100644 --- a/packages/cli/src/cli/config.rs +++ b/packages/cli/src/cli/config.rs @@ -1,4 +1,5 @@ -use dioxus_cli_config::crate_root; +use crate::build::TargetArgs; +use crate::{metadata::crate_root, CliSettings}; use super::*; @@ -70,7 +71,7 @@ impl Config { return Ok(()); } let mut file = File::create(conf_path)?; - let content = String::from(include_str!("../assets/dioxus.toml")) + let content = String::from(include_str!("../../assets/dioxus.toml")) .replace("{{project-name}}", &name) .replace("{{default-platform}}", &platform); file.write_all(content.as_bytes())?; @@ -79,29 +80,21 @@ impl Config { Config::FormatPrint {} => { println!( "{:#?}", - dioxus_cli_config::CrateConfig::new(None)?.dioxus_config + crate::dioxus_crate::DioxusCrate::new(&TargetArgs::default())?.dioxus_config ); } Config::CustomHtml {} => { let html_path = crate_root.join("index.html"); let mut file = File::create(html_path)?; - let content = include_str!("../assets/index.html"); + let content = include_str!("../../assets/index.html"); file.write_all(content.as_bytes())?; tracing::info!("🚩 Create custom html file done."); } Config::SetGlobal { setting, value } => { - let mut global_settings = - dioxus_cli_config::CliSettings::from_global().unwrap_or_default(); - - match setting { - Setting::AlwaysHotReload => { - global_settings.always_hot_reload = Some(value.into()) - } - Setting::AlwaysOpenBrowser => { - global_settings.always_open_browser = Some(value.into()) - } - } - global_settings.save().unwrap(); + CliSettings::modify_settings(|settings| match setting { + Setting::AlwaysHotReload => settings.always_hot_reload = Some(value.into()), + Setting::AlwaysOpenBrowser => settings.always_open_browser = Some(value.into()), + })?; } } Ok(()) diff --git a/packages/cli/src/cli/create.rs b/packages/cli/src/cli/create.rs index 3b1644ed7..fbf63cf21 100644 --- a/packages/cli/src/cli/create.rs +++ b/packages/cli/src/cli/create.rs @@ -63,7 +63,7 @@ pub fn post_create(path: &PathBuf) -> Result<()> { let toml_paths = [path.join("Cargo.toml"), path.join("Dioxus.toml")]; for toml_path in &toml_paths { let toml = std::fs::read_to_string(toml_path)?; - let mut toml = toml.parse::().map_err(|e| { + let mut toml = toml.parse::().map_err(|e| { anyhow::anyhow!( "failed to parse toml at {}: {}", toml_path.display(), diff --git a/packages/cli/src/cli/mod.rs b/packages/cli/src/cli/mod.rs index 24d2f3c63..6f0caf96a 100644 --- a/packages/cli/src/cli/mod.rs +++ b/packages/cli/src/cli/mod.rs @@ -1,25 +1,17 @@ pub mod autoformat; pub mod build; pub mod bundle; -pub mod cfg; pub mod check; pub mod clean; pub mod config; pub mod create; pub mod init; pub mod link; -pub mod plugin; pub mod serve; pub mod translate; -use crate::{ - cfg::{ConfigOptsBuild, ConfigOptsServe}, - custom_error, - error::Result, - gen_page, server, Error, -}; +use crate::{custom_error, error::Result, Error}; use clap::{Parser, Subcommand}; -use dioxus_cli_config::CrateConfig; use html_parser::Dom; use serde::Deserialize; use std::{ @@ -82,11 +74,6 @@ pub enum Commands { #[clap(subcommand)] Config(config::Config), - /// Manage plugins for dioxus cli - #[cfg(feature = "plugin")] - #[clap(subcommand)] - Plugin(plugin::Plugin), - /// Handles parsing of linker arguments for linker-based systems /// such as Manganis and binary patching. Link(link::LinkCommand), @@ -106,9 +93,6 @@ impl Display for Commands { Commands::Check(_) => write!(f, "check"), Commands::Bundle(_) => write!(f, "bundle"), Commands::Link(_) => write!(f, "link"), - - #[cfg(feature = "plugin")] - Commands::Plugin(_) => write!(f, "plugin"), } } } diff --git a/packages/cli/src/cli/plugin.rs b/packages/cli/src/cli/plugin.rs deleted file mode 100644 index ccff887cd..000000000 --- a/packages/cli/src/cli/plugin.rs +++ /dev/null @@ -1,39 +0,0 @@ -#![cfg(feature = "plugin")] - -use super::*; - -/// Manage plugins for dioxus cli -#[derive(Clone, Debug, Deserialize, Subcommand)] -#[clap(name = "plugin")] -pub enum Plugin { - /// Return all dioxus-cli support tools. - List {}, - /// Get default app install path. - AppPath {}, - /// Install a new tool. - Add { name: String }, -} - -impl Plugin { - pub fn plugin(self) -> Result<()> { - match self { - Plugin::List {} => { - for item in crate::plugin::PluginManager::plugin_list() { - println!("- {item}"); - } - } - Plugin::AppPath {} => { - let plugin_dir = crate::plugin::PluginManager::init_plugin_dir(); - if let Some(v) = plugin_dir.to_str() { - println!("{}", v); - } else { - tracing::error!("Plugin path get failed."); - } - } - Plugin::Add { name: _ } => { - tracing::info!("You can use `dx plugin app-path` to get Installation position"); - } - } - Ok(()) - } -} diff --git a/packages/cli/src/cli/serve.rs b/packages/cli/src/cli/serve.rs index f2437ac6d..4183af9ce 100644 --- a/packages/cli/src/cli/serve.rs +++ b/packages/cli/src/cli/serve.rs @@ -1,110 +1,91 @@ -use dioxus_cli_config::Platform; -use manganis_cli_support::AssetManifest; +use crate::{ + settings::{self}, + DioxusCrate, +}; +use anyhow::Context; +use build::Build; +use dioxus_cli_config::AddressArguments; +use std::ops::Deref; use super::*; -use cargo_toml::Dependency::{Detailed, Inherited, Simple}; -use std::fs::create_dir_all; + +/// Arguments for the serve command +#[derive(Clone, Debug, Parser, Default)] +pub struct ServeArguments { + /// The arguments for the address the server will run on + #[clap(flatten)] + pub address: AddressArguments, + + /// Open the app in the default browser [default: false - unless project or global settings are set] + #[arg(long, default_missing_value="true", num_args=0..=1)] + pub open: Option, + + /// Enable full hot reloading for the app [default: true - unless project or global settings are set] + #[clap(long, group = "release-incompatible")] + pub hot_reload: Option, + + /// Set cross-origin-policy to same-origin [default: false] + #[clap(name = "cross-origin-policy")] + #[clap(long)] + pub cross_origin_policy: bool, + + /// Additional arguments to pass to the executable + #[clap(long)] + pub args: Vec, +} /// Run the WASM project on dev-server -#[derive(Clone, Debug, Parser)] +#[derive(Clone, Debug, Default, Parser)] +#[command(group = clap::ArgGroup::new("release-incompatible").multiple(true).conflicts_with("release"))] #[clap(name = "serve")] pub struct Serve { + /// Arguments for the serve command #[clap(flatten)] - pub serve: ConfigOptsServe, + pub(crate) server_arguments: ServeArguments, + + /// Arguments for the dioxus build + #[clap(flatten)] + pub(crate) build_arguments: Build, + + /// Run the server in interactive mode + #[arg(long, default_missing_value="true", num_args=0..=1, short = 'i')] + pub interactive: Option, } impl Serve { - pub fn serve(self, bin: Option) -> Result<()> { - let mut crate_config = dioxus_cli_config::CrateConfig::new(bin)?; - let mut serve_cfg = self.serve.clone(); - - // Handle cli settings - let cli_settings = crate_config.dioxus_config.cli_settings.clone().unwrap(); - - if serve_cfg.hot_reload.is_none() { - // we're going to override the hot_reload setting in the project's cfg based on settings - // - // let hot_reload = self.serve.hot_reload || crate_config.dioxus_config.application.hot_reload; - - let value = cli_settings.always_hot_reload.unwrap_or(true); - serve_cfg.hot_reload = Some(value); - crate_config.with_hot_reload(value); - } - - if serve_cfg.open.is_none() { - serve_cfg.open = Some(cli_settings.always_open_browser.unwrap_or(false)); - } - + /// Resolve the serve arguments from the arguments or the config + fn resolve(&mut self, crate_config: &mut DioxusCrate) -> Result<()> { // Set config settings - crate_config.with_cross_origin_policy(self.serve.cross_origin_policy); - crate_config.with_release(self.serve.release); - crate_config.with_verbose(self.serve.verbose); + let settings = settings::CliSettings::load(); - if let Some(example) = self.serve.example { - crate_config.as_example(example); + if self.server_arguments.hot_reload.is_none() { + self.server_arguments.hot_reload = Some(settings.always_hot_reload.unwrap_or(true)); + } + if self.server_arguments.open.is_none() { + self.server_arguments.open = Some(settings.always_open_browser.unwrap_or_default()); } - if let Some(profile) = self.serve.profile { - crate_config.set_profile(profile); - } - - if let Some(features) = self.serve.features { - crate_config.set_features(features); - } - - if let Some(target) = self.serve.target { - crate_config.set_target(target); - } - - crate_config.set_cargo_args(self.serve.cargo_args); - - let mut platform = self.serve.platform; - - if platform.is_none() { - if let Some(dependency) = &crate_config.manifest.dependencies.get("dioxus") { - let features = match dependency { - Inherited(detail) => detail.features.to_vec(), - Detailed(detail) => detail.features.to_vec(), - Simple(_) => vec![], - }; - - platform = features - .iter() - .find_map(|platform| serde_json::from_str(&format!(r#""{}""#, platform)).ok()); - } - } - - let platform = platform.unwrap_or(crate_config.dioxus_config.application.default_platform); - crate_config.extend_with_platform(platform); - - // start the develop server - use server::{desktop, fullstack, web}; - match platform { - Platform::Web => web::startup(crate_config.clone(), &serve_cfg)?, - Platform::Desktop => desktop::startup(crate_config.clone(), &serve_cfg)?, - Platform::Fullstack | Platform::StaticGeneration => { - fullstack::startup(crate_config.clone(), &serve_cfg)? - } - _ => unreachable!(), - } + // Resolve the build arguments + self.build_arguments.resolve(crate_config)?; Ok(()) } - pub fn regen_dev_page( - crate_config: &CrateConfig, - manifest: Option<&AssetManifest>, - ) -> anyhow::Result<()> { - let serve_html = gen_page(crate_config, manifest, true); + pub async fn serve(mut self) -> anyhow::Result<()> { + let mut dioxus_crate = DioxusCrate::new(&self.build_arguments.target_args) + .context("Failed to load Dioxus workspace")?; - let dist_path = crate_config.out_dir(); - if !dist_path.is_dir() { - create_dir_all(&dist_path)?; - } - let index_path = dist_path.join("index.html"); - let mut file = std::fs::File::create(index_path)?; - file.write_all(serve_html.as_bytes())?; + self.resolve(&mut dioxus_crate)?; + crate::serve::serve_all(self, dioxus_crate).await?; Ok(()) } } + +impl Deref for Serve { + type Target = Build; + + fn deref(&self) -> &Self::Target { + &self.build_arguments + } +} diff --git a/packages/cli/src/cli/translate.rs b/packages/cli/src/cli/translate.rs index 01c72a042..4c8b48518 100644 --- a/packages/cli/src/cli/translate.rs +++ b/packages/cli/src/cli/translate.rs @@ -1,6 +1,6 @@ use std::process::exit; -use dioxus_rsx::{BodyNode, CallBody}; +use dioxus_rsx::{BodyNode, CallBody, TemplateBody}; use super::*; @@ -59,7 +59,7 @@ pub fn convert_html_to_formatted_rsx(dom: &Dom, component: bool) -> String { fn write_callbody_with_icon_section(mut callbody: CallBody) -> String { let mut svgs = vec![]; - rsx_rosetta::collect_svgs(&mut callbody.roots, &mut svgs); + rsx_rosetta::collect_svgs(&mut callbody.body.roots, &mut svgs); let mut out = write_component_body(dioxus_autofmt::write_block_out(&callbody).unwrap()); @@ -81,7 +81,8 @@ fn write_svg_section(out: &mut String, svgs: Vec) { out.push_str("\n\nmod icons {"); out.push_str("\n use super::*;"); for (idx, icon) in svgs.into_iter().enumerate() { - let raw = dioxus_autofmt::write_block_out(&CallBody { roots: vec![icon] }).unwrap(); + let raw = + dioxus_autofmt::write_block_out(&CallBody::new(TemplateBody::new(vec![icon]))).unwrap(); out.push_str("\n\n pub fn icon_"); out.push_str(&idx.to_string()); out.push_str("() -> Element {\n rsx! {"); diff --git a/packages/cli/src/dioxus_crate.rs b/packages/cli/src/dioxus_crate.rs new file mode 100644 index 000000000..22818295f --- /dev/null +++ b/packages/cli/src/dioxus_crate.rs @@ -0,0 +1,370 @@ +use crate::build::TargetArgs; +use dioxus_cli_config::{DioxusConfig, Platform}; +use krates::cm::Target; +use krates::{cm::TargetKind, Cmd, Krates, NodeId}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use std::{ + fmt::{Display, Formatter}, + path::PathBuf, +}; + +use crate::metadata::CargoError; + +/// Load the dioxus config from a path +fn load_dioxus_config( + krates: &Krates, + package: NodeId, +) -> Result, CrateConfigError> { + fn acquire_dioxus_toml(dir: &std::path::Path) -> Option { + ["Dioxus.toml", "dioxus.toml"] + .into_iter() + .map(|file| dir.join(file)) + .find(|path| path.is_file()) + } + + // Walk up from the cargo.toml to the root of the workspace looking for Dioxus.toml + let mut current_dir = krates[package] + .manifest_path + .parent() + .unwrap() + .as_std_path() + .to_path_buf() + .canonicalize()?; + let workspace_path = krates + .workspace_root() + .as_std_path() + .to_path_buf() + .canonicalize()?; + + let mut dioxus_conf_file = None; + while current_dir.starts_with(&workspace_path) { + // Try to find Dioxus.toml in the current directory + if let Some(new_config) = acquire_dioxus_toml(¤t_dir) { + dioxus_conf_file = Some(new_config.as_path().to_path_buf()); + break; + } + // If we can't find it, go up a directory + current_dir = current_dir + .parent() + .ok_or(CrateConfigError::CurrentPackageNotFound)? + .to_path_buf(); + } + + let Some(dioxus_conf_file) = dioxus_conf_file else { + return Ok(None); + }; + + let cfg = toml::from_str::(&std::fs::read_to_string(&dioxus_conf_file)?) + .map_err(|err| { + CrateConfigError::LoadDioxusConfig(LoadDioxusConfigError { + location: dioxus_conf_file.display().to_string(), + error: err.to_string(), + }) + }) + .map(Some); + match cfg { + Ok(Some(mut cfg)) => { + let name = cfg.application.name.clone(); + if cfg.bundle.identifier.is_none() { + cfg.bundle.identifier = Some(format!("io.github.{name}")); + } + if cfg.bundle.publisher.is_none() { + cfg.bundle.publisher = Some(name); + } + + Ok(Some(cfg)) + } + cfg => cfg, + } +} + +// Find the main package in the workspace +fn find_main_package(package: Option, krates: &Krates) -> Result { + let kid = match package { + Some(package) => { + let mut workspace_members = krates.workspace_members(); + workspace_members + .find_map(|node| { + if let krates::Node::Krate { id, krate, .. } = node { + if krate.name == package { + return Some(id); + } + } + None + }) + .ok_or_else(|| CrateConfigError::PackageNotFound(package.clone()))? + } + None => { + // Otherwise find the package that is the closest parent of the current directory + let current_dir = std::env::current_dir()?; + let current_dir = current_dir.as_path(); + // Go through each member and find the path that is a parent of the current directory + let mut closest_parent = None; + for member in krates.workspace_members() { + if let krates::Node::Krate { id, krate, .. } = member { + let member_path = krate.manifest_path.parent().unwrap(); + if let Ok(path) = current_dir.strip_prefix(member_path.as_std_path()) { + let len = path.components().count(); + match closest_parent { + Some((_, closest_parent_len)) => { + if len < closest_parent_len { + closest_parent = Some((id, len)); + } + } + None => { + closest_parent = Some((id, len)); + } + } + } + } + } + closest_parent + .map(|(id, _)| id) + .ok_or(CrateConfigError::CurrentPackageNotFound)? + } + }; + + let package = krates.nid_for_kid(kid).unwrap(); + Ok(package) +} + +// Contains information about the crate we are currently in and the dioxus config for that crate +#[derive(Clone)] +pub struct DioxusCrate { + pub krates: Arc, + pub package: NodeId, + pub dioxus_config: DioxusConfig, + pub target: Target, +} + +impl DioxusCrate { + pub fn new(target: &TargetArgs) -> Result { + let mut cmd = Cmd::new(); + cmd.features(target.features.clone()); + let builder = krates::Builder::new(); + let krates = builder.build(cmd, |_| {})?; + let package = find_main_package(target.package.clone(), &krates)?; + + let dioxus_config = load_dioxus_config(&krates, package)?.unwrap_or_default(); + + let package_name = krates[package].name.clone(); + let target_kind = if target.example.is_some() { + TargetKind::Example + } else { + TargetKind::Bin + }; + let target_name = target + .example + .clone() + .or(target.bin.clone()) + .unwrap_or(package_name); + let main_package = &krates[package]; + let target = main_package + .targets + .iter() + .find(|target| { + target_name == target.name.as_str() && target.kind.contains(&target_kind) + }) + .ok_or(CrateConfigError::TargetNotFound(target_name))? + .clone(); + + Ok(Self { + krates: Arc::new(krates), + package, + dioxus_config, + target, + }) + } + + /// Compose an asset directory. Represents the typical "public" directory + /// with publicly available resources (configurable in the `Dioxus.toml`). + pub fn asset_dir(&self) -> PathBuf { + self.workspace_dir() + .join(&self.dioxus_config.application.asset_dir) + } + + /// Compose an out directory. Represents the typical "dist" directory that + /// is "distributed" after building an application (configurable in the + /// `Dioxus.toml`). + pub fn out_dir(&self) -> PathBuf { + self.workspace_dir() + .join(&self.dioxus_config.application.out_dir) + } + + /// Compose an out directory for the fullstack platform. See `out_dir()` + /// method. + pub fn fullstack_out_dir(&self) -> PathBuf { + self.workspace_dir().join(".dioxus") + } + + /// Compose a target directory for the server (fullstack-only?). + pub fn server_target_dir(&self) -> PathBuf { + self.fullstack_out_dir().join("ssr") + } + + /// Compose a target directory for the client (fullstack-only?). + pub fn client_target_dir(&self) -> PathBuf { + self.fullstack_out_dir().join("web") + } + + /// Get the workspace directory for the crate + pub fn workspace_dir(&self) -> PathBuf { + self.krates.workspace_root().as_std_path().to_path_buf() + } + + /// Get the directory of the crate + pub fn crate_dir(&self) -> PathBuf { + self.package() + .manifest_path + .parent() + .unwrap() + .as_std_path() + .to_path_buf() + } + + /// Get the main source file of the target + pub fn main_source_file(&self) -> PathBuf { + self.target.src_path.as_std_path().to_path_buf() + } + + /// Get the package we are currently in + pub fn package(&self) -> &krates::cm::Package { + &self.krates[self.package] + } + + /// Get the name of the package we are compiling + pub fn executable_name(&self) -> &str { + &self.target.name + } + + /// Get the type of executable we are compiling + pub fn executable_type(&self) -> krates::cm::TargetKind { + self.target.kind[0].clone() + } + + pub fn features_for_platform(&mut self, platform: Platform) -> Vec { + let package = self.package(); + // Try to find the feature that activates the dioxus feature for the given platform + let dioxus_feature = platform.feature_name(); + let feature = package.features.iter().find_map(|(key, features)| { + // Find a feature that starts with dioxus/ or dioxus?/ + for feature in features { + if let Some((_, after_dioxus)) = feature.split_once("dioxus") { + if let Some(dioxus_feature_enabled) = + after_dioxus.trim_start_matches('?').strip_prefix('/') + { + // If that enables the feature we are looking for, return that feature + if dioxus_feature_enabled == dioxus_feature { + return Some(key.clone()); + } + } + } + } + None + }); + + feature.into_iter().collect() + } + + /// Check if assets should be pre_compressed. This will only be true in release mode if the user has enabled pre_compress in the web config. + pub fn should_pre_compress_web_assets(&self, release: bool) -> bool { + self.dioxus_config.web.pre_compress && release + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Executable { + pub name: String, + pub ty: ExecutableType, +} + +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +pub enum ExecutableType { + Binary, + Lib, + Example, +} + +impl ExecutableType { + /// Get the name of the executable if it is a binary or an example. + pub fn executable(&self) -> bool { + matches!(self, Self::Binary | Self::Example) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LoadDioxusConfigError { + location: String, + error: String, +} + +impl std::fmt::Display for LoadDioxusConfigError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} {}", self.location, self.error) + } +} + +impl std::error::Error for LoadDioxusConfigError {} + +#[derive(Debug)] +#[non_exhaustive] +pub enum CrateConfigError { + Cargo(CargoError), + Io(std::io::Error), + Toml(toml::de::Error), + LoadDioxusConfig(LoadDioxusConfigError), + TargetNotFound(String), + Krates(krates::Error), + PackageNotFound(String), + CurrentPackageNotFound, +} + +impl From for CrateConfigError { + fn from(err: CargoError) -> Self { + Self::Cargo(err) + } +} + +impl From for CrateConfigError { + fn from(err: std::io::Error) -> Self { + Self::Io(err) + } +} + +impl From for CrateConfigError { + fn from(err: toml::de::Error) -> Self { + Self::Toml(err) + } +} + +impl From for CrateConfigError { + fn from(err: LoadDioxusConfigError) -> Self { + Self::LoadDioxusConfig(err) + } +} + +impl From for CrateConfigError { + fn from(err: krates::Error) -> Self { + Self::Krates(err) + } +} + +impl Display for CrateConfigError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Cargo(err) => write!(f, "{}", err), + Self::Io(err) => write!(f, "{}", err), + Self::Toml(err) => write!(f, "{}", err), + Self::LoadDioxusConfig(err) => write!(f, "{}", err), + Self::TargetNotFound(target) => { + write!(f, "Failed to find target with name: {}", target) + } + Self::Krates(err) => write!(f, "{}", err), + Self::PackageNotFound(package) => write!(f, "Package not found: {}", package), + Self::CurrentPackageNotFound => write!(f, "Failed to find current package"), + } + } +} + +impl std::error::Error for CrateConfigError {} diff --git a/packages/cli/src/dx_build_info.rs b/packages/cli/src/dx_build_info.rs new file mode 100644 index 000000000..d11fb6389 --- /dev/null +++ b/packages/cli/src/dx_build_info.rs @@ -0,0 +1,2 @@ +// The file has been placed there by the build script. +include!(concat!(env!("OUT_DIR"), "/built.rs")); diff --git a/packages/cli/src/error.rs b/packages/cli/src/error.rs index 35eea7eab..0de3b7041 100644 --- a/packages/cli/src/error.rs +++ b/packages/cli/src/error.rs @@ -1,5 +1,7 @@ use thiserror::Error as ThisError; +use crate::{metadata::CargoError, CrateConfigError, LoadDioxusConfigError}; + pub type Result = std::result::Result; #[derive(ThisError, Debug)] @@ -72,20 +74,20 @@ impl From for Error { } } -impl From for Error { - fn from(e: dioxus_cli_config::LoadDioxusConfigError) -> Self { +impl From for Error { + fn from(e: LoadDioxusConfigError) -> Self { Self::RuntimeError(e.to_string()) } } -impl From for Error { - fn from(e: dioxus_cli_config::CargoError) -> Self { +impl From for Error { + fn from(e: CargoError) -> Self { Self::CargoError(e.to_string()) } } -impl From for Error { - fn from(e: dioxus_cli_config::CrateConfigError) -> Self { +impl From for Error { + fn from(e: CrateConfigError) -> Self { Self::RuntimeError(e.to_string()) } } diff --git a/packages/cli/src/lib.rs b/packages/cli/src/lib.rs index b4df0f8e1..442a7c114 100644 --- a/packages/cli/src/lib.rs +++ b/packages/cli/src/lib.rs @@ -3,17 +3,22 @@ #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")] pub mod assets; -pub mod builder; -pub mod server; +pub mod dx_build_info; +pub mod serve; pub mod tools; -pub use builder::*; - pub mod cli; pub use cli::*; pub mod error; pub use error::*; -#[cfg(feature = "plugin")] -pub mod plugin; +pub(crate) mod builder; + +mod dioxus_crate; +pub use dioxus_crate::*; + +mod settings; +pub(crate) use settings::*; + +pub(crate) mod metadata; diff --git a/packages/cli/src/main.rs b/packages/cli/src/main.rs index 4232d0e51..62bd976ad 100644 --- a/packages/cli/src/main.rs +++ b/packages/cli/src/main.rs @@ -1,6 +1,5 @@ -use dioxus_cli_config::DioxusConfig; -use std::{env, path::PathBuf}; -use tracing_subscriber::EnvFilter; +use std::env; +use tracing_subscriber::{prelude::*, EnvFilter, Layer}; use anyhow::Context; use clap::Parser; @@ -10,16 +9,11 @@ use Commands::*; const LOG_ENV: &str = "DIOXUS_LOG"; -fn main() -> anyhow::Result<()> { +#[tokio::main] +async fn main() -> anyhow::Result<()> { let args = Cli::parse(); - // If {LOG_ENV} is set, default to env, otherwise filter to cli - // and manganis warnings and errors from other crates - let mut filter = EnvFilter::new("error,dx=info,dioxus-cli=info,manganis-cli-support=info"); - if env::var(LOG_ENV).is_ok() { - filter = EnvFilter::from_env(LOG_ENV); - } - tracing_subscriber::fmt().with_env_filter(filter).init(); + build_tracing(); match args.action { Translate(opts) => opts @@ -32,89 +26,65 @@ fn main() -> anyhow::Result<()> { Init(opts) => opts .init() - .context(error_wrapper("Initialising a new project failed")), + .context(error_wrapper("Initializing a new project failed")), Config(opts) => opts .config() .context(error_wrapper("Configuring new project failed")), - #[cfg(feature = "plugin")] - Plugin(opts) => opts.plugin().context(error_wrapper("Error with plugin")), - Autoformat(opts) => opts .autoformat() .context(error_wrapper("Error autoformatting RSX")), - Check(opts) => opts.check().context(error_wrapper("Error checking RSX")), + Check(opts) => opts + .check() + .await + .context(error_wrapper("Error checking RSX")), Link(opts) => opts .link() .context(error_wrapper("Error with linker passthrough")), - action => { - let bin = get_bin(args.bin)?; - let _dioxus_config = DioxusConfig::load(Some(bin.clone())) - .context("Failed to load Dioxus config because")? - .unwrap_or_else(|| { - tracing::info!("You appear to be creating a Dioxus project from scratch; we will use the default config"); - DioxusConfig::default() - }); + Build(mut opts) => opts + .run() + .await + .context(error_wrapper("Building project failed")), - #[cfg(feature = "plugin")] - use dioxus_cli::plugin::PluginManager; + Clean(opts) => opts + .clean() + .context(error_wrapper("Cleaning project failed")), - #[cfg(feature = "plugin")] - PluginManager::init(_dioxus_config.plugin) - .context(error_wrapper("Plugin system initialization failed"))?; + Serve(opts) => opts + .serve() + .await + .context(error_wrapper("Serving project failed")), - match action { - Build(opts) => opts - .build(Some(bin.clone()), None, None) - .context(error_wrapper("Building project failed")), - - Clean(opts) => opts - .clean(Some(bin.clone())) - .context(error_wrapper("Cleaning project failed")), - - Serve(opts) => opts - .serve(Some(bin.clone())) - .context(error_wrapper("Serving project failed")), - - Bundle(opts) => opts - .bundle(Some(bin.clone())) - .context(error_wrapper("Bundling project failed")), - - _ => unreachable!(), - } - } + Bundle(opts) => opts + .bundle() + .await + .context(error_wrapper("Bundling project failed")), } } -fn get_bin(bin: Option) -> Result { - let metadata = cargo_metadata::MetadataCommand::new() - .exec() - .map_err(Error::CargoMetadata)?; - let package = if let Some(bin) = bin { - metadata - .workspace_packages() - .into_iter() - .find(|p| p.name == bin) - .ok_or(Error::CargoError(format!("no such package: {}", bin)))? - } else { - metadata - .root_package() - .ok_or(Error::CargoError("no root package?".to_string()))? - }; - - let crate_dir = package - .manifest_path - .parent() - .ok_or(Error::CargoError("couldn't take parent dir".to_string()))?; - - Ok(crate_dir.into()) -} - /// Simplifies error messages that use the same pattern. fn error_wrapper(message: &str) -> String { format!("🚫 {message}:") } + +fn build_tracing() { + // If {LOG_ENV} is set, default to env, otherwise filter to cli + // and manganis warnings and errors from other crates + let mut filter = EnvFilter::new("error,dx=info,dioxus-cli=info,manganis-cli-support=info"); + if env::var(LOG_ENV).is_ok() { + filter = EnvFilter::from_env(LOG_ENV); + } + + let sub = + tracing_subscriber::registry().with(tracing_subscriber::fmt::layer().with_filter(filter)); + + #[cfg(feature = "tokio-console")] + sub.with(console_subscriber::spawn()).init(); + + #[cfg(not(feature = "tokio-console"))] + sub.init(); +} diff --git a/packages/cli/src/metadata.rs b/packages/cli/src/metadata.rs new file mode 100644 index 000000000..b9acbdf75 --- /dev/null +++ b/packages/cli/src/metadata.rs @@ -0,0 +1,64 @@ +//! Utilities for working with cargo and rust files +use std::error::Error; +use std::{ + env, + fmt::{Display, Formatter}, + fs, + path::{Path, PathBuf}, +}; + +#[derive(Debug, Clone)] +pub struct CargoError { + msg: String, +} + +impl CargoError { + pub fn new(msg: String) -> Self { + Self { msg } + } +} + +impl Display for CargoError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "CargoError: {}", self.msg) + } +} + +impl Error for CargoError {} + +/// How many parent folders are searched for a `Cargo.toml` +const MAX_ANCESTORS: u32 = 10; + +/// Returns the root of the crate that the command is run from +/// +/// If the command is run from the workspace root, this will return the top-level Cargo.toml +pub(crate) fn crate_root() -> Result { + // From the current directory we work our way up, looking for `Cargo.toml` + env::current_dir() + .ok() + .and_then(|mut wd| { + for _ in 0..MAX_ANCESTORS { + if contains_manifest(&wd) { + return Some(wd); + } + if !wd.pop() { + break; + } + } + None + }) + .ok_or_else(|| { + CargoError::new("Failed to find directory containing Cargo.toml".to_string()) + }) +} + +/// Checks if the directory contains `Cargo.toml` +fn contains_manifest(path: &Path) -> bool { + fs::read_dir(path) + .map(|entries| { + entries + .filter_map(Result::ok) + .any(|ent| &ent.file_name() == "Cargo.toml") + }) + .unwrap_or(false) +} diff --git a/packages/cli/src/plugin/interface/command.rs b/packages/cli/src/plugin/interface/command.rs deleted file mode 100644 index 19a8205a7..000000000 --- a/packages/cli/src/plugin/interface/command.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::process::{Command, Stdio}; - -use mlua::{FromLua, UserData}; - -#[derive(Debug, Clone, Copy)] -enum StdioFromString { - Inherit, - Piped, - Null, -} -impl<'lua> FromLua<'lua> for StdioFromString { - fn from_lua(lua_value: mlua::Value<'lua>, _lua: &'lua mlua::Lua) -> mlua::Result { - if let mlua::Value::String(v) = lua_value { - let v = v.to_str().unwrap(); - return Ok(match v.to_lowercase().as_str() { - "inherit" => Self::Inherit, - "piped" => Self::Piped, - "null" => Self::Null, - _ => Self::Inherit, - }); - } - Ok(Self::Inherit) - } -} -impl StdioFromString { - pub fn to_stdio(self) -> Stdio { - match self { - StdioFromString::Inherit => Stdio::inherit(), - StdioFromString::Piped => Stdio::piped(), - StdioFromString::Null => Stdio::null(), - } - } -} - -pub struct PluginCommander; -impl UserData for PluginCommander { - fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_function( - "exec", - |_, args: (Vec, StdioFromString, StdioFromString)| { - let cmd = args.0; - let stdout = args.1; - let stderr = args.2; - - if cmd.is_empty() { - return Ok(()); - } - let cmd_name = cmd.first().unwrap(); - let mut command = Command::new(cmd_name); - let t = cmd - .iter() - .enumerate() - .filter(|(i, _)| *i > 0) - .map(|v| v.1.clone()) - .collect::>(); - command.args(t); - command.stdout(stdout.to_stdio()).stderr(stderr.to_stdio()); - command.output()?; - Ok(()) - }, - ); - } - - fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(_fields: &mut F) {} -} diff --git a/packages/cli/src/plugin/interface/dirs.rs b/packages/cli/src/plugin/interface/dirs.rs deleted file mode 100644 index afce56b5c..000000000 --- a/packages/cli/src/plugin/interface/dirs.rs +++ /dev/null @@ -1,13 +0,0 @@ -use mlua::UserData; - -use crate::tools::app_path; - -pub struct PluginDirs; -impl UserData for PluginDirs { - fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_function("plugins_dir", |_, ()| { - let path = app_path().join("plugins"); - Ok(path.to_str().unwrap().to_string()) - }); - } -} diff --git a/packages/cli/src/plugin/interface/fs.rs b/packages/cli/src/plugin/interface/fs.rs deleted file mode 100644 index 5668dbe33..000000000 --- a/packages/cli/src/plugin/interface/fs.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::{ - fs::{create_dir, create_dir_all, remove_dir_all, File}, - io::{Read, Write}, - path::PathBuf, -}; - -use crate::tools::extract_zip; -use flate2::read::GzDecoder; -use mlua::UserData; -use tar::Archive; - -pub struct PluginFileSystem; -impl UserData for PluginFileSystem { - fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_function("create_dir", |_, args: (String, bool)| { - let path = args.0; - let recursive = args.1; - let path = PathBuf::from(path); - if !path.exists() { - let v = if recursive { - create_dir_all(path) - } else { - create_dir(path) - }; - return Ok(v.is_ok()); - } - Ok(true) - }); - methods.add_function("remove_dir", |_, path: String| { - let path = PathBuf::from(path); - let r = remove_dir_all(path); - Ok(r.is_ok()) - }); - methods.add_function("file_get_content", |_, path: String| { - let path = PathBuf::from(path); - let mut file = std::fs::File::open(path)?; - let mut buffer = String::new(); - file.read_to_string(&mut buffer)?; - Ok(buffer) - }); - methods.add_function("file_set_content", |_, args: (String, String)| { - let path = args.0; - let content = args.1; - let path = PathBuf::from(path); - - let file = std::fs::File::create(path); - if file.is_err() { - return Ok(false); - } - - if file.unwrap().write_all(content.as_bytes()).is_err() { - return Ok(false); - } - - Ok(true) - }); - methods.add_function("unzip_file", |_, args: (String, String)| { - let file = PathBuf::from(args.0); - let target = PathBuf::from(args.1); - let res = extract_zip(&file, &target); - if res.is_err() { - return Ok(false); - } - Ok(true) - }); - methods.add_function("untar_gz_file", |_, args: (String, String)| { - let file = PathBuf::from(args.0); - let target = PathBuf::from(args.1); - - let tar_gz = if let Ok(v) = File::open(file) { - v - } else { - return Ok(false); - }; - - let tar = GzDecoder::new(tar_gz); - let mut archive = Archive::new(tar); - if archive.unpack(&target).is_err() { - return Ok(false); - } - - Ok(true) - }); - } -} diff --git a/packages/cli/src/plugin/interface/log.rs b/packages/cli/src/plugin/interface/log.rs deleted file mode 100644 index dbf65bb8d..000000000 --- a/packages/cli/src/plugin/interface/log.rs +++ /dev/null @@ -1,27 +0,0 @@ -use mlua::UserData; - -pub struct PluginLogger; -impl UserData for PluginLogger { - fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_function("trace", |_, info: String| { - tracing::trace!("{}", info); - Ok(()) - }); - methods.add_function("info", |_, info: String| { - tracing::info!("{}", info); - Ok(()) - }); - methods.add_function("debug", |_, info: String| { - tracing::debug!("{}", info); - Ok(()) - }); - methods.add_function("warn", |_, info: String| { - tracing::warn!("{}", info); - Ok(()) - }); - methods.add_function("error", |_, info: String| { - tracing::error!("{}", info); - Ok(()) - }); - } -} diff --git a/packages/cli/src/plugin/interface/mod.rs b/packages/cli/src/plugin/interface/mod.rs deleted file mode 100644 index 5ed2ed827..000000000 --- a/packages/cli/src/plugin/interface/mod.rs +++ /dev/null @@ -1,233 +0,0 @@ -use mlua::{FromLua, Function, ToLua}; - -pub mod command; -pub mod dirs; -pub mod fs; -pub mod log; -pub mod network; -pub mod os; -pub mod path; - -#[derive(Debug, Clone)] -pub struct PluginInfo<'lua> { - pub name: String, - pub repository: String, - pub author: String, - pub version: String, - - pub inner: PluginInner, - - pub on_init: Option>, - pub build: PluginBuildInfo<'lua>, - pub serve: PluginServeInfo<'lua>, -} - -impl<'lua> FromLua<'lua> for PluginInfo<'lua> { - fn from_lua(lua_value: mlua::Value<'lua>, _lua: &'lua mlua::Lua) -> mlua::Result { - let mut res = Self { - name: String::default(), - repository: String::default(), - author: String::default(), - version: String::from("0.1.0"), - - inner: Default::default(), - - on_init: None, - build: Default::default(), - serve: Default::default(), - }; - if let mlua::Value::Table(tab) = lua_value { - if let Ok(v) = tab.get::<_, String>("name") { - res.name = v; - } - if let Ok(v) = tab.get::<_, String>("repository") { - res.repository = v; - } - if let Ok(v) = tab.get::<_, String>("author") { - res.author = v; - } - if let Ok(v) = tab.get::<_, String>("version") { - res.version = v; - } - - if let Ok(v) = tab.get::<_, PluginInner>("inner") { - res.inner = v; - } - - if let Ok(v) = tab.get::<_, Function>("on_init") { - res.on_init = Some(v); - } - - if let Ok(v) = tab.get::<_, PluginBuildInfo>("build") { - res.build = v; - } - - if let Ok(v) = tab.get::<_, PluginServeInfo>("serve") { - res.serve = v; - } - } - - Ok(res) - } -} - -impl<'lua> ToLua<'lua> for PluginInfo<'lua> { - fn to_lua(self, lua: &'lua mlua::Lua) -> mlua::Result> { - let res = lua.create_table()?; - - res.set("name", self.name.to_string())?; - res.set("repository", self.repository.to_string())?; - res.set("author", self.author.to_string())?; - res.set("version", self.version.to_string())?; - - res.set("inner", self.inner)?; - - if let Some(e) = self.on_init { - res.set("on_init", e)?; - } - res.set("build", self.build)?; - res.set("serve", self.serve)?; - - Ok(mlua::Value::Table(res)) - } -} - -#[derive(Debug, Clone, Default)] -pub struct PluginInner { - pub plugin_dir: String, - pub from_loader: bool, -} - -impl<'lua> FromLua<'lua> for PluginInner { - fn from_lua(lua_value: mlua::Value<'lua>, _lua: &'lua mlua::Lua) -> mlua::Result { - let mut res = Self { - plugin_dir: String::new(), - from_loader: false, - }; - - if let mlua::Value::Table(t) = lua_value { - if let Ok(v) = t.get::<_, String>("plugin_dir") { - res.plugin_dir = v; - } - if let Ok(v) = t.get::<_, bool>("from_loader") { - res.from_loader = v; - } - } - Ok(res) - } -} - -impl<'lua> ToLua<'lua> for PluginInner { - fn to_lua(self, lua: &'lua mlua::Lua) -> mlua::Result> { - let res = lua.create_table()?; - - res.set("plugin_dir", self.plugin_dir)?; - res.set("from_loader", self.from_loader)?; - - Ok(mlua::Value::Table(res)) - } -} - -#[derive(Debug, Clone, Default)] -pub struct PluginBuildInfo<'lua> { - pub on_start: Option>, - pub on_finish: Option>, -} - -impl<'lua> FromLua<'lua> for PluginBuildInfo<'lua> { - fn from_lua(lua_value: mlua::Value<'lua>, _lua: &'lua mlua::Lua) -> mlua::Result { - let mut res = Self { - on_start: None, - on_finish: None, - }; - - if let mlua::Value::Table(t) = lua_value { - if let Ok(v) = t.get::<_, Function>("on_start") { - res.on_start = Some(v); - } - if let Ok(v) = t.get::<_, Function>("on_finish") { - res.on_finish = Some(v); - } - } - - Ok(res) - } -} - -impl<'lua> ToLua<'lua> for PluginBuildInfo<'lua> { - fn to_lua(self, lua: &'lua mlua::Lua) -> mlua::Result> { - let res = lua.create_table()?; - - if let Some(v) = self.on_start { - res.set("on_start", v)?; - } - - if let Some(v) = self.on_finish { - res.set("on_finish", v)?; - } - - Ok(mlua::Value::Table(res)) - } -} - -#[derive(Debug, Clone, Default)] -pub struct PluginServeInfo<'lua> { - pub interval: i32, - - pub on_start: Option>, - pub on_interval: Option>, - pub on_rebuild: Option>, - pub on_shutdown: Option>, -} - -impl<'lua> FromLua<'lua> for PluginServeInfo<'lua> { - fn from_lua(lua_value: mlua::Value<'lua>, _lua: &'lua mlua::Lua) -> mlua::Result { - let mut res = Self::default(); - - if let mlua::Value::Table(tab) = lua_value { - if let Ok(v) = tab.get::<_, i32>("interval") { - res.interval = v; - } - if let Ok(v) = tab.get::<_, Function>("on_start") { - res.on_start = Some(v); - } - if let Ok(v) = tab.get::<_, Function>("on_interval") { - res.on_interval = Some(v); - } - if let Ok(v) = tab.get::<_, Function>("on_rebuild") { - res.on_rebuild = Some(v); - } - if let Ok(v) = tab.get::<_, Function>("on_shutdown") { - res.on_shutdown = Some(v); - } - } - - Ok(res) - } -} - -impl<'lua> ToLua<'lua> for PluginServeInfo<'lua> { - fn to_lua(self, lua: &'lua mlua::Lua) -> mlua::Result> { - let res = lua.create_table()?; - - res.set("interval", self.interval)?; - - if let Some(v) = self.on_start { - res.set("on_start", v)?; - } - - if let Some(v) = self.on_interval { - res.set("on_interval", v)?; - } - - if let Some(v) = self.on_rebuild { - res.set("on_rebuild", v)?; - } - - if let Some(v) = self.on_shutdown { - res.set("on_shutdown", v)?; - } - - Ok(mlua::Value::Table(res)) - } -} diff --git a/packages/cli/src/plugin/interface/network.rs b/packages/cli/src/plugin/interface/network.rs deleted file mode 100644 index 8d940ddc4..000000000 --- a/packages/cli/src/plugin/interface/network.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::{io::Cursor, path::PathBuf}; - -use mlua::UserData; - -pub struct PluginNetwork; -impl UserData for PluginNetwork { - fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_function("download_file", |_, args: (String, String)| { - let url = args.0; - let path = args.1; - - let resp = reqwest::blocking::get(url); - if let Ok(resp) = resp { - let mut content = Cursor::new(resp.bytes().unwrap()); - let file = std::fs::File::create(PathBuf::from(path)); - if file.is_err() { - return Ok(false); - } - let mut file = file.unwrap(); - let res = std::io::copy(&mut content, &mut file); - return Ok(res.is_ok()); - } - - Ok(false) - }); - } -} diff --git a/packages/cli/src/plugin/interface/os.rs b/packages/cli/src/plugin/interface/os.rs deleted file mode 100644 index 73295d930..000000000 --- a/packages/cli/src/plugin/interface/os.rs +++ /dev/null @@ -1,18 +0,0 @@ -use mlua::UserData; - -pub struct PluginOS; -impl UserData for PluginOS { - fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_function("current_platform", |_, ()| { - if cfg!(target_os = "windows") { - Ok("windows") - } else if cfg!(target_os = "macos") { - Ok("macos") - } else if cfg!(target_os = "linux") { - Ok("linux") - } else { - panic!("unsupported platformm"); - } - }); - } -} diff --git a/packages/cli/src/plugin/interface/path.rs b/packages/cli/src/plugin/interface/path.rs deleted file mode 100644 index dcc9071b5..000000000 --- a/packages/cli/src/plugin/interface/path.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::path::PathBuf; - -use mlua::{UserData, Variadic}; - -pub struct PluginPath; -impl UserData for PluginPath { - fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { - // join function - methods.add_function("join", |_, args: Variadic| { - let mut path = PathBuf::new(); - for i in args { - path = path.join(i); - } - Ok(path.to_str().unwrap().to_string()) - }); - - // parent function - methods.add_function("parent", |_, path: String| { - let current_path = PathBuf::from(&path); - let parent = current_path.parent(); - - if let Some(parent) = parent { - Ok(parent.to_str().unwrap().to_string()) - } else { - Ok(path) - } - }); - methods.add_function("exists", |_, path: String| { - let path = PathBuf::from(path); - Ok(path.exists()) - }); - methods.add_function("is_dir", |_, path: String| { - let path = PathBuf::from(path); - Ok(path.is_dir()) - }); - methods.add_function("is_file", |_, path: String| { - let path = PathBuf::from(path); - Ok(path.is_file()) - }); - } -} diff --git a/packages/cli/src/plugin/mod.rs b/packages/cli/src/plugin/mod.rs deleted file mode 100644 index 82267a784..000000000 --- a/packages/cli/src/plugin/mod.rs +++ /dev/null @@ -1,328 +0,0 @@ -use std::{ - io::{Read, Write}, - path::PathBuf, - sync::Mutex, -}; - -use crate::tools::{app_path, clone_repo}; -use dioxus_cli_config::CrateConfig; -use mlua::{Lua, Table}; -use serde_json::json; - -use self::{ - interface::{ - command::PluginCommander, dirs::PluginDirs, fs::PluginFileSystem, log::PluginLogger, - network::PluginNetwork, os::PluginOS, path::PluginPath, PluginInfo, - }, - types::PluginConfig, -}; - -pub mod interface; -mod types; - -lazy_static::lazy_static! { - static ref LUA: Mutex = Mutex::new(Lua::new()); -} - -pub struct PluginManager; - -impl PluginManager { - pub fn init(config: toml::Value) -> anyhow::Result<()> { - let config = PluginConfig::from_toml_value(config); - - if !config.available { - return Ok(()); - } - - let lua = LUA.lock().unwrap(); - - let manager = lua.create_table().unwrap(); - let name_index = lua.create_table().unwrap(); - - let plugin_dir = Self::init_plugin_dir(); - - let api = lua.create_table().unwrap(); - - api.set("log", PluginLogger).unwrap(); - api.set("command", PluginCommander).unwrap(); - api.set("network", PluginNetwork).unwrap(); - api.set("dirs", PluginDirs).unwrap(); - api.set("fs", PluginFileSystem).unwrap(); - api.set("path", PluginPath).unwrap(); - api.set("os", PluginOS).unwrap(); - - lua.globals().set("plugin_lib", api).unwrap(); - lua.globals() - .set("library_dir", plugin_dir.to_str().unwrap()) - .unwrap(); - lua.globals().set("config_info", config.clone())?; - - let mut index: u32 = 1; - let dirs = std::fs::read_dir(&plugin_dir)?; - - let mut path_list = dirs - .filter(|v| v.is_ok()) - .map(|v| (v.unwrap().path(), false)) - .collect::>(); - for i in &config.loader { - let path = PathBuf::from(i); - if !path.is_dir() { - // for loader dir, we need check first, because we need give a error log. - tracing::error!("Plugin loader: {:?} path is not a exists directory.", path); - } - path_list.push((path, true)); - } - - for entry in path_list { - let plugin_dir = entry.0.to_path_buf(); - - if plugin_dir.is_dir() { - let init_file = plugin_dir.join("init.lua"); - if init_file.is_file() { - let mut file = std::fs::File::open(init_file).unwrap(); - let mut buffer = String::new(); - file.read_to_string(&mut buffer).unwrap(); - - let current_plugin_dir = plugin_dir.to_str().unwrap().to_string(); - let from_loader = entry.1; - - lua.globals() - .set("_temp_plugin_dir", current_plugin_dir.clone())?; - lua.globals().set("_temp_from_loader", from_loader)?; - - let info = lua.load(&buffer).eval::(); - match info { - Ok(mut info) => { - if name_index.contains_key(info.name.clone()).unwrap_or(false) - && !from_loader - { - // found same name plugin, intercept load - tracing::warn!( - "Plugin {} has been intercepted. [mulit-load]", - info.name - ); - continue; - } - info.inner.plugin_dir = current_plugin_dir; - info.inner.from_loader = from_loader; - - // call `on_init` if file "dcp.json" not exists - let dcp_file = plugin_dir.join("dcp.json"); - if !dcp_file.is_file() { - if let Some(func) = info.clone().on_init { - let result = func.call::<_, bool>(()); - match result { - Ok(true) => { - // plugin init success, create `dcp.json` file. - let mut file = std::fs::File::create(dcp_file).unwrap(); - let value = json!({ - "name": info.name, - "author": info.author, - "repository": info.repository, - "version": info.version, - "generate_time": chrono::Local::now().timestamp(), - }); - let buffer = - serde_json::to_string_pretty(&value).unwrap(); - let buffer = buffer.as_bytes(); - file.write_all(buffer).unwrap(); - - // insert plugin-info into plugin-manager - if let Ok(index) = - name_index.get::<_, u32>(info.name.clone()) - { - let _ = manager.set(index, info.clone()); - } else { - let _ = manager.set(index, info.clone()); - index += 1; - let _ = name_index.set(info.name, index); - } - } - Ok(false) => { - tracing::warn!( - "Plugin init function result is `false`, init failed." - ); - } - Err(e) => { - tracing::warn!("Plugin init failed: {e}"); - } - } - } - } else if let Ok(index) = name_index.get::<_, u32>(info.name.clone()) { - let _ = manager.set(index, info.clone()); - } else { - let _ = manager.set(index, info.clone()); - index += 1; - let _ = name_index.set(info.name, index); - } - } - Err(_e) => { - let dir_name = plugin_dir.file_name().unwrap().to_str().unwrap(); - tracing::error!("Plugin '{dir_name}' load failed."); - } - } - } - } - } - - lua.globals().set("manager", manager).unwrap(); - - Ok(()) - } - - pub fn on_build_start(crate_config: &CrateConfig, platform: &str) -> anyhow::Result<()> { - let lua = LUA.lock().unwrap(); - - if !lua.globals().contains_key("manager")? { - return Ok(()); - } - let manager = lua.globals().get::<_, Table>("manager")?; - - let args = lua.create_table()?; - args.set("name", crate_config.dioxus_config.application.name.clone())?; - args.set("platform", platform)?; - args.set("out_dir", crate_config.out_dir().to_str().unwrap())?; - args.set("asset_dir", crate_config.asset_dir().to_str().unwrap())?; - - for i in 1..(manager.len()? as i32 + 1) { - let info = manager.get::(i)?; - if let Some(func) = info.build.on_start { - func.call::(args.clone())?; - } - } - - Ok(()) - } - - pub fn on_build_finish(crate_config: &CrateConfig, platform: &str) -> anyhow::Result<()> { - let lua = LUA.lock().unwrap(); - - if !lua.globals().contains_key("manager")? { - return Ok(()); - } - let manager = lua.globals().get::<_, Table>("manager")?; - - let args = lua.create_table()?; - args.set("name", crate_config.dioxus_config.application.name.clone())?; - args.set("platform", platform)?; - args.set("out_dir", crate_config.out_dir().to_str().unwrap())?; - args.set("asset_dir", crate_config.asset_dir().to_str().unwrap())?; - - for i in 1..(manager.len()? as i32 + 1) { - let info = manager.get::(i)?; - if let Some(func) = info.build.on_finish { - func.call::(args.clone())?; - } - } - - Ok(()) - } - - pub fn on_serve_start(crate_config: &CrateConfig) -> anyhow::Result<()> { - let lua = LUA.lock().unwrap(); - - if !lua.globals().contains_key("manager")? { - return Ok(()); - } - let manager = lua.globals().get::<_, Table>("manager")?; - - let args = lua.create_table()?; - args.set("name", crate_config.dioxus_config.application.name.clone())?; - - for i in 1..(manager.len()? as i32 + 1) { - let info = manager.get::(i)?; - if let Some(func) = info.serve.on_start { - func.call::(args.clone())?; - } - } - - Ok(()) - } - - pub fn on_serve_rebuild(timestamp: i64, files: Vec) -> anyhow::Result<()> { - let lua = LUA.lock().unwrap(); - - let manager = lua.globals().get::<_, Table>("manager")?; - - let args = lua.create_table()?; - args.set("timestamp", timestamp)?; - let files: Vec = files - .iter() - .map(|v| v.to_str().unwrap().to_string()) - .collect(); - args.set("changed_files", files)?; - - for i in 1..(manager.len()? as i32 + 1) { - let info = manager.get::(i)?; - if let Some(func) = info.serve.on_rebuild { - func.call::(args.clone())?; - } - } - - Ok(()) - } - - pub fn on_serve_shutdown(crate_config: &CrateConfig) -> anyhow::Result<()> { - let lua = LUA.lock().unwrap(); - - if !lua.globals().contains_key("manager")? { - return Ok(()); - } - let manager = lua.globals().get::<_, Table>("manager")?; - - let args = lua.create_table()?; - args.set("name", crate_config.dioxus_config.application.name.clone())?; - - for i in 1..(manager.len()? as i32 + 1) { - let info = manager.get::(i)?; - if let Some(func) = info.serve.on_shutdown { - func.call::(args.clone())?; - } - } - - Ok(()) - } - - pub fn init_plugin_dir() -> PathBuf { - let app_path = app_path(); - let plugin_path = app_path.join("plugins"); - if !plugin_path.is_dir() { - tracing::info!("📖 Start to init plugin library ..."); - let url = "https://github.com/DioxusLabs/cli-plugin-library"; - if let Err(err) = clone_repo(&plugin_path, url) { - tracing::error!("Failed to init plugin dir, error caused by {}. ", err); - } - } - plugin_path - } - - pub fn plugin_list() -> Vec { - let mut res = vec![]; - - if let Ok(lua) = LUA.lock() { - let list = lua - .load(mlua::chunk!( - local list = {} - for key, value in ipairs(manager) do - table.insert(list, {name = value.name, loader = value.inner.from_loader}) - end - return list - )) - .eval::>() - .unwrap_or_default(); - for i in list { - let name = i.get::<_, String>("name").unwrap(); - let loader = i.get::<_, bool>("loader").unwrap(); - - let text = if loader { - format!("{name} [:loader]") - } else { - name - }; - res.push(text); - } - } - - res - } -} diff --git a/packages/cli/src/plugin/types.rs b/packages/cli/src/plugin/types.rs deleted file mode 100644 index d22c1423c..000000000 --- a/packages/cli/src/plugin/types.rs +++ /dev/null @@ -1,138 +0,0 @@ -use std::collections::HashMap; - -use mlua::ToLua; - -#[derive(Debug, Clone)] -pub struct PluginConfig { - pub available: bool, - pub loader: Vec, - pub config_info: HashMap>, -} - -impl<'lua> ToLua<'lua> for PluginConfig { - fn to_lua(self, lua: &'lua mlua::Lua) -> mlua::Result> { - let table = lua.create_table()?; - - table.set("available", self.available)?; - table.set("loader", self.loader)?; - - let config_info = lua.create_table()?; - - for (name, data) in self.config_info { - config_info.set(name, data)?; - } - - table.set("config_info", config_info)?; - - Ok(mlua::Value::Table(table)) - } -} - -impl PluginConfig { - pub fn from_toml_value(val: toml::Value) -> Self { - if let toml::Value::Table(tab) = val { - let available = tab - .get::<_>("available") - .unwrap_or(&toml::Value::Boolean(true)); - let available = available.as_bool().unwrap_or(true); - - let mut loader = vec![]; - if let Some(origin) = tab.get("loader") { - if origin.is_array() { - for i in origin.as_array().unwrap() { - loader.push(i.as_str().unwrap_or_default().to_string()); - } - } - } - - let mut config_info = HashMap::new(); - - for (name, value) in tab { - if name == "available" || name == "loader" { - continue; - } - if let toml::Value::Table(value) = value { - let mut map = HashMap::new(); - for (item, info) in value { - map.insert(item, Value::from_toml(info)); - } - config_info.insert(name, map); - } - } - - Self { - available, - loader, - config_info, - } - } else { - Self { - available: false, - loader: vec![], - config_info: HashMap::new(), - } - } - } -} - -#[allow(dead_code)] -#[derive(Debug, Clone)] -pub enum Value { - String(String), - Integer(i64), - Float(f64), - Boolean(bool), - Array(Vec), - Table(HashMap), -} - -impl Value { - pub fn from_toml(origin: toml::Value) -> Self { - match origin { - cargo_toml::Value::String(s) => Value::String(s), - cargo_toml::Value::Integer(i) => Value::Integer(i), - cargo_toml::Value::Float(f) => Value::Float(f), - cargo_toml::Value::Boolean(b) => Value::Boolean(b), - cargo_toml::Value::Datetime(d) => Value::String(d.to_string()), - cargo_toml::Value::Array(a) => { - let mut v = vec![]; - for i in a { - v.push(Value::from_toml(i)); - } - Value::Array(v) - } - cargo_toml::Value::Table(t) => { - let mut h = HashMap::new(); - for (n, v) in t { - h.insert(n, Value::from_toml(v)); - } - Value::Table(h) - } - } - } -} - -impl<'lua> ToLua<'lua> for Value { - fn to_lua(self, lua: &'lua mlua::Lua) -> mlua::Result> { - Ok(match self { - Value::String(s) => mlua::Value::String(lua.create_string(&s)?), - Value::Integer(i) => mlua::Value::Integer(i), - Value::Float(f) => mlua::Value::Number(f), - Value::Boolean(b) => mlua::Value::Boolean(b), - Value::Array(a) => { - let table = lua.create_table()?; - for (i, v) in a.iter().enumerate() { - table.set(i, v.clone())?; - } - mlua::Value::Table(table) - } - Value::Table(t) => { - let table = lua.create_table()?; - for (i, v) in t.iter() { - table.set(i.clone(), v.clone())?; - } - mlua::Value::Table(table) - } - }) - } -} diff --git a/packages/cli/src/serve/builder.rs b/packages/cli/src/serve/builder.rs new file mode 100644 index 000000000..7e1424656 --- /dev/null +++ b/packages/cli/src/serve/builder.rs @@ -0,0 +1,167 @@ +use crate::builder::BuildRequest; +use crate::builder::BuildResult; +use crate::builder::UpdateBuildProgress; +use crate::dioxus_crate::DioxusCrate; +use crate::serve::Serve; +use crate::Result; +use dioxus_cli_config::Platform; +use futures_channel::mpsc::UnboundedReceiver; +use futures_util::future::OptionFuture; +use futures_util::stream::select_all; +use futures_util::StreamExt; +use std::process::Stdio; +use tokio::{ + process::{Child, Command}, + task::JoinHandle, +}; + +/// A handle to ongoing builds and then the spawned tasks themselves +pub struct Builder { + /// The results of the build + build_results: Option>>>, + + /// The progress of the builds + build_progress: Vec<(Platform, UnboundedReceiver)>, + + /// The application we are building + config: DioxusCrate, + + /// The arguments for the build + serve: Serve, + + /// The children of the build process + pub children: Vec<(Platform, Child)>, +} + +impl Builder { + /// Create a new builder + pub fn new(config: &DioxusCrate, serve: &Serve) -> Self { + let serve = serve.clone(); + let config = config.clone(); + Self { + build_results: None, + build_progress: Vec::new(), + config: config.clone(), + serve, + children: Vec::new(), + } + } + + /// Start a new build - killing the current one if it exists + pub fn build(&mut self) { + self.shutdown(); + let build_requests = + BuildRequest::create(true, &self.config, self.serve.build_arguments.clone()); + + let mut set = tokio::task::JoinSet::new(); + + for build_request in build_requests { + let (mut tx, rx) = futures_channel::mpsc::unbounded(); + self.build_progress + .push((build_request.build_arguments.platform(), rx)); + set.spawn(async move { + let res = build_request.build(tx.clone()).await; + + if let Err(err) = &res { + let _ = tx.start_send(UpdateBuildProgress { + stage: crate::builder::Stage::Finished, + update: crate::builder::UpdateStage::Failed(err.to_string()), + }); + } + + res + }); + } + + self.build_results = Some(tokio::spawn(async move { + let mut all_results = Vec::new(); + while let Some(result) = set.join_next().await { + let res = result.map_err(|err| { + crate::Error::Unique(format!("Panic while building project: {err:?}")) + })??; + + all_results.push(res); + } + Ok(all_results) + })); + } + + /// Wait for any new updates to the builder - either it completed or gave us a message etc + pub async fn wait(&mut self) -> Result { + // Wait for build progress + let mut next = select_all( + self.build_progress + .iter_mut() + .map(|(platform, rx)| rx.map(move |update| (*platform, update))), + ); + + let results: OptionFuture<_> = self.build_results.as_mut().into(); + + // Wait for the next build result + tokio::select! { + Some(build_results) = results => { + self.build_results = None; + + // If we have a build result, bubble it up to the main loop + let build_results = build_results.map_err(|_| crate::Error::Unique("Build join failed".to_string()))??; + + Ok(BuilderUpdate::Ready { results: build_results }) + } + Some((platform, update)) = next.next() => { + // If we have a build progress, send it to the screen + Ok(BuilderUpdate::Progress { platform, update }) + } + else => { + std::future::pending::<()>().await; + unreachable!("Pending cannot resolve") + }, + } + } + + /// Shutdown the current build process + pub(crate) fn shutdown(&mut self) { + for (_, mut child) in self.children.drain(..) { + // Gracefully shtudown the desktop app + // It might have a receiver to do some cleanup stuff + if let Some(pid) = child.id() { + // on unix, we can send a signal to the process to shut down + #[cfg(unix)] + { + _ = Command::new("kill") + .args(["-s", "TERM", &pid.to_string()]) + .stderr(Stdio::null()) + .stdout(Stdio::null()) + .spawn(); + } + + // on windows, use the `taskkill` command + #[cfg(windows)] + { + _ = Command::new("taskkill") + .args(["/F", "/PID", &pid.to_string()]) + .stderr(Stdio::null()) + .stdout(Stdio::null()) + .spawn(); + } + } + + // Todo: add a timeout here to kill the process if it doesn't shut down within a reasonable time + _ = child.start_kill(); + } + + if let Some(tasks) = self.build_results.take() { + tasks.abort(); + } + self.build_progress.clear(); + } +} + +pub enum BuilderUpdate { + Progress { + platform: Platform, + update: UpdateBuildProgress, + }, + Ready { + results: Vec, + }, +} diff --git a/packages/rsx/src/hot_reload/hot_reloading_file_map.rs b/packages/cli/src/serve/hot_reloading_file_map.rs similarity index 59% rename from packages/rsx/src/hot_reload/hot_reloading_file_map.rs rename to packages/cli/src/serve/hot_reloading_file_map.rs index c0fe960f8..1cec478b4 100644 --- a/packages/rsx/src/hot_reload/hot_reloading_file_map.rs +++ b/packages/cli/src/serve/hot_reloading_file_map.rs @@ -1,44 +1,25 @@ -use super::{ - hot_reload_diff::{diff_rsx, DiffResult}, - ChangedRsx, -}; -use crate::{CallBody, HotReloadingContext}; -use dioxus_core::{ - prelude::{TemplateAttribute, TemplateNode}, - Template, +use dioxus_core::{internal::HotReloadLiteral, Template}; +use dioxus_rsx::{ + hot_reload::{diff_rsx, ChangedRsx}, + CallBody, HotReloadedTemplate, HotReloadingContext, }; use krates::cm::MetadataCommand; use krates::Cmd; -pub use proc_macro2::TokenStream; pub use std::collections::HashMap; -pub use std::sync::Mutex; -pub use std::time::SystemTime; -use std::{collections::HashSet, ffi::OsStr, marker::PhantomData, path::PathBuf}; +use std::{ffi::OsStr, path::PathBuf}; pub use std::{fs, io, path::Path}; pub use std::{fs::File, io::Read}; use syn::spanned::Spanned; -pub enum UpdateResult { - UpdatedRsx(Vec