add basic crash analytics and very light telemetry to dioxus-cli (#4224)
* wip * wip: basic telemetry * fix merge * keep errors for telemetry * fix upload when queued events are empty * add per session id and keep track of build timing * keep track of what commands are run * capture panics * keep track of panics * better path stripping * better tracing error telemetry * only sync every week and check settings before sending any telemetry * keep track of the args passed to commands * anonymize commands * more error tolerant * wip * wip * wip * merge cnflict * other merg conflict * migrate everything from telemetry to logging * update docs * wip... about to merge trace controller * lil bit more refactor to clean up tracer * more performant log writer * cleanup ci detecting * wire up to posthog * re-uploads work * yoink out the anon from each file * cleanup sig * lil bit cleaner * dont write empty files * wip log handler * fmt * cleanup logging of backtraces * add proper backtrace support * use stable reporter * adhere to posthog format * fix stacktraces * use anyhow for capturing backtraces * I think we're done? * cleanup logging * properly collect! * move out keycode handling * she works * typo * add extra fields * fix cargo doc --------- Co-authored-by: Evan Almloff <evanalmloff@gmail.com>
This commit is contained in:
1 parent
a610aaefc3
commit
e43bf05b69
30 files changed
+1748
-441
No files matched your search
Generated
+93
@@ -5073,6 +5073,7 @@ dependencies = [
|
|||||||
"dioxus-check",
|
"dioxus-check",
|
||||||
"dioxus-cli-config",
|
"dioxus-cli-config",
|
||||||
"dioxus-cli-opt",
|
"dioxus-cli-opt",
|
||||||
|
"dioxus-cli-telemetry",
|
||||||
"dioxus-core",
|
"dioxus-core",
|
||||||
"dioxus-core-types",
|
"dioxus-core-types",
|
||||||
"dioxus-devtools-types",
|
"dioxus-devtools-types",
|
||||||
@@ -5088,6 +5089,7 @@ dependencies = [
|
|||||||
"env_logger 0.11.8",
|
"env_logger 0.11.8",
|
||||||
"escargot",
|
"escargot",
|
||||||
"flate2",
|
"flate2",
|
||||||
|
"fs2",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"handlebars",
|
"handlebars",
|
||||||
@@ -5112,6 +5114,7 @@ dependencies = [
|
|||||||
"path-absolutize",
|
"path-absolutize",
|
||||||
"pdb",
|
"pdb",
|
||||||
"plist",
|
"plist",
|
||||||
|
"posthog-rs",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
@@ -5121,8 +5124,10 @@ dependencies = [
|
|||||||
"rustls 0.23.29",
|
"rustls 0.23.29",
|
||||||
"self-replace",
|
"self-replace",
|
||||||
"self_update",
|
"self_update",
|
||||||
|
"sentry-backtrace",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_json5",
|
||||||
"shell-words",
|
"shell-words",
|
||||||
"strum 0.27.1",
|
"strum 0.27.1",
|
||||||
"subsecond-types",
|
"subsecond-types",
|
||||||
@@ -5221,6 +5226,18 @@ dependencies = [
|
|||||||
"tar",
|
"tar",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dioxus-cli-telemetry"
|
||||||
|
version = "0.7.0-alpha.3"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"dirs 6.0.0",
|
||||||
|
"rand 0.9.1",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-config-macro"
|
name = "dioxus-config-macro"
|
||||||
version = "0.7.0-alpha.3"
|
version = "0.7.0-alpha.3"
|
||||||
@@ -6990,6 +7007,16 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fs2"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fs_at"
|
name = "fs_at"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -12477,6 +12504,21 @@ dependencies = [
|
|||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "posthog-rs"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ead24e7bd3ce35a88fffe1f9a4747b4844335fa837eddecf9e7ad9708f3e26b3"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"derive_builder",
|
||||||
|
"reqwest 0.11.27",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "potential_utf"
|
name = "potential_utf"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -14178,6 +14220,46 @@ dependencies = [
|
|||||||
"futures-core",
|
"futures-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sentry-backtrace"
|
||||||
|
version = "0.42.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68e299dd3f7bcf676875eee852c9941e1d08278a743c32ca528e2debf846a653"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
"regex",
|
||||||
|
"sentry-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sentry-core"
|
||||||
|
version = "0.42.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "deaa38b94e70820ff3f1f9db3c8b0aef053b667be130f618e615e0ff2492cbcc"
|
||||||
|
dependencies = [
|
||||||
|
"sentry-types",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sentry-types"
|
||||||
|
version = "0.42.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e477f4d4db08ddb4ab553717a8d3a511bc9e81dde0c808c680feacbb8105c412"
|
||||||
|
dependencies = [
|
||||||
|
"debugid",
|
||||||
|
"hex",
|
||||||
|
"rand 0.9.1",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 2.0.12",
|
||||||
|
"time",
|
||||||
|
"url",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "separator"
|
name = "separator"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@@ -14278,6 +14360,17 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json5"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d34d03f54462862f2a42918391c9526337f53171eaa4d8894562be7f252edd3"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_derive",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_path_to_error"
|
name = "serde_path_to_error"
|
||||||
version = "0.1.17"
|
version = "0.1.17"
|
||||||
|
|||||||
+14
-6
@@ -6,6 +6,8 @@ members = [
|
|||||||
"packages/core-types",
|
"packages/core-types",
|
||||||
"packages/cli",
|
"packages/cli",
|
||||||
"packages/cli-opt",
|
"packages/cli-opt",
|
||||||
|
"packages/cli-config",
|
||||||
|
"packages/cli-telemetry",
|
||||||
"packages/core-types",
|
"packages/core-types",
|
||||||
"packages/core-macro",
|
"packages/core-macro",
|
||||||
"packages/config-macro",
|
"packages/config-macro",
|
||||||
@@ -22,8 +24,6 @@ members = [
|
|||||||
"packages/liveview",
|
"packages/liveview",
|
||||||
"packages/autofmt",
|
"packages/autofmt",
|
||||||
"packages/check",
|
"packages/check",
|
||||||
"packages/cli-config",
|
|
||||||
"packages/cli",
|
|
||||||
"packages/config-macro",
|
"packages/config-macro",
|
||||||
"packages/core-macro",
|
"packages/core-macro",
|
||||||
"packages/core-types",
|
"packages/core-types",
|
||||||
@@ -140,6 +140,7 @@ version = "0.7.0-alpha.3"
|
|||||||
# dependencies that are shared across packages
|
# dependencies that are shared across packages
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
dioxus = { path = "packages/dioxus", version = "0.7.0-alpha.3" }
|
dioxus = { path = "packages/dioxus", version = "0.7.0-alpha.3" }
|
||||||
|
dioxus-lib = { path = "packages/dioxus-lib", version = "0.7.0-alpha.3" }
|
||||||
dioxus-core = { path = "packages/core", version = "0.7.0-alpha.3" }
|
dioxus-core = { path = "packages/core", version = "0.7.0-alpha.3" }
|
||||||
dioxus-core-types = { path = "packages/core-types", version = "0.7.0-alpha.3" }
|
dioxus-core-types = { path = "packages/core-types", version = "0.7.0-alpha.3" }
|
||||||
dioxus-core-macro = { path = "packages/core-macro", version = "0.7.0-alpha.3" }
|
dioxus-core-macro = { path = "packages/core-macro", version = "0.7.0-alpha.3" }
|
||||||
@@ -155,6 +156,7 @@ dioxus-web = { path = "packages/web", version = "0.7.0-alpha.3", default-feature
|
|||||||
dioxus-isrg = { path = "packages/isrg", version = "0.7.0-alpha.3" }
|
dioxus-isrg = { path = "packages/isrg", version = "0.7.0-alpha.3" }
|
||||||
dioxus-ssr = { path = "packages/ssr", version = "0.7.0-alpha.3", default-features = false }
|
dioxus-ssr = { path = "packages/ssr", version = "0.7.0-alpha.3", default-features = false }
|
||||||
dioxus-desktop = { path = "packages/desktop", version = "0.7.0-alpha.3", default-features = false }
|
dioxus-desktop = { path = "packages/desktop", version = "0.7.0-alpha.3", default-features = false }
|
||||||
|
dioxus-mobile = { path = "packages/mobile", version = "0.7.0-alpha.3" }
|
||||||
dioxus-interpreter-js = { path = "packages/interpreter", version = "0.7.0-alpha.3" }
|
dioxus-interpreter-js = { path = "packages/interpreter", version = "0.7.0-alpha.3" }
|
||||||
dioxus-liveview = { path = "packages/liveview", version = "0.7.0-alpha.3" }
|
dioxus-liveview = { path = "packages/liveview", version = "0.7.0-alpha.3" }
|
||||||
dioxus-autofmt = { path = "packages/autofmt", version = "0.7.0-alpha.3" }
|
dioxus-autofmt = { path = "packages/autofmt", version = "0.7.0-alpha.3" }
|
||||||
@@ -165,8 +167,6 @@ dioxus-rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.7.0-alpha.3"
|
|||||||
dioxus-signals = { path = "packages/signals", version = "0.7.0-alpha.3" }
|
dioxus-signals = { path = "packages/signals", version = "0.7.0-alpha.3" }
|
||||||
dioxus-stores = { path = "packages/stores", version = "0.7.0-alpha.3" }
|
dioxus-stores = { path = "packages/stores", version = "0.7.0-alpha.3" }
|
||||||
dioxus-stores-macro = { path = "packages/stores-macro", version = "0.7.0-alpha.3" }
|
dioxus-stores-macro = { path = "packages/stores-macro", version = "0.7.0-alpha.3" }
|
||||||
dioxus-cli-config = { path = "packages/cli-config", version = "0.7.0-alpha.3" }
|
|
||||||
dioxus-cli-opt = { path = "packages/cli-opt", version = "0.7.0-alpha.3" }
|
|
||||||
dioxus-devtools = { path = "packages/devtools", version = "0.7.0-alpha.3" }
|
dioxus-devtools = { path = "packages/devtools", version = "0.7.0-alpha.3" }
|
||||||
dioxus-devtools-types = { path = "packages/devtools-types", version = "0.7.0-alpha.3" }
|
dioxus-devtools-types = { path = "packages/devtools-types", version = "0.7.0-alpha.3" }
|
||||||
dioxus-server = { path = "packages/server", version = "0.7.0-alpha.3" }
|
dioxus-server = { path = "packages/server", version = "0.7.0-alpha.3" }
|
||||||
@@ -180,11 +180,19 @@ dioxus-native = { path = "packages/native", version = "0.7.0-alpha.3" }
|
|||||||
dioxus-native-dom = { path = "packages/native-dom", version = "0.7.0-alpha.3" }
|
dioxus-native-dom = { path = "packages/native-dom", version = "0.7.0-alpha.3" }
|
||||||
dioxus-asset-resolver = { path = "packages/asset-resolver", version = "0.7.0-alpha.3" }
|
dioxus-asset-resolver = { path = "packages/asset-resolver", version = "0.7.0-alpha.3" }
|
||||||
dioxus-config-macros = { path = "packages/config-macros", version = "0.7.0-alpha.3" }
|
dioxus-config-macros = { path = "packages/config-macros", version = "0.7.0-alpha.3" }
|
||||||
const-serialize = { path = "packages/const-serialize", version = "0.7.0-alpha.3" }
|
|
||||||
const-serialize-macro = { path = "packages/const-serialize-macro", version = "0.7.0-alpha.3" }
|
|
||||||
generational-box = { path = "packages/generational-box", version = "0.7.0-alpha.3" }
|
generational-box = { path = "packages/generational-box", version = "0.7.0-alpha.3" }
|
||||||
lazy-js-bundle = { path = "packages/lazy-js-bundle", version = "0.7.0-alpha.3" }
|
lazy-js-bundle = { path = "packages/lazy-js-bundle", version = "0.7.0-alpha.3" }
|
||||||
|
|
||||||
|
# cli
|
||||||
|
dioxus-cli-opt = { path = "packages/cli-opt", version = "0.7.0-alpha.0" }
|
||||||
|
dioxus-cli-telemetry = { path = "packages/cli-telemetry", version = "0.7.0-alpha.0" }
|
||||||
|
dioxus-cli-config = { path = "packages/cli-config", version = "0.7.0-alpha.0" }
|
||||||
|
|
||||||
|
# const-serializea
|
||||||
|
const-serialize = { path = "packages/const-serialize", version = "0.7.0-alpha.0" }
|
||||||
|
const-serialize-macro = { path = "packages/const-serialize-macro", version = "0.7.0-alpha.0" }
|
||||||
|
|
||||||
# subsecond
|
# subsecond
|
||||||
subsecond-types = { path = "packages/subsecond/subsecond-types", version = "0.7.0-alpha.3" }
|
subsecond-types = { path = "packages/subsecond/subsecond-types", version = "0.7.0-alpha.3" }
|
||||||
subsecond = { path = "packages/subsecond/subsecond", version = "0.7.0-alpha.3" }
|
subsecond = { path = "packages/subsecond/subsecond", version = "0.7.0-alpha.3" }
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "dioxus-cli-telemetry"
|
||||||
|
edition = "2021"
|
||||||
|
version.workspace = true
|
||||||
|
description = "Wire format for the dioxus CLI telemetry type"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
|
rand = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
dirs = { workspace = true }
|
||||||
|
uuid = { workspace = true, features = ["serde", "v4"] }
|
||||||
@@ -0,0 +1,272 @@
|
|||||||
|
//! # Telemetry for the Dioxus CLI
|
||||||
|
//!
|
||||||
|
//! Dioxus uses telemetry in the CLI to get insight into metrics like performance, panics, and usage
|
||||||
|
//! of various arguments. This data helps us track down bugs and improve quality of the tooling.
|
||||||
|
//!
|
||||||
|
//! Usage of telemetry in open source products can be controversial. Our goal here is to collect
|
||||||
|
//! minimally invasive data used exclusively to improve our tooling. Github issues only show *some*
|
||||||
|
//! of the problem, but many users stumble into issues which go unreported.
|
||||||
|
//!
|
||||||
|
//! Our policy follows:
|
||||||
|
//! - minimally invasive
|
||||||
|
//! - anonymous
|
||||||
|
//! - periodic
|
||||||
|
//! - transparent
|
||||||
|
//! - easy to disable
|
||||||
|
//!
|
||||||
|
//! We send a heartbeat when the CLI is executed and then rollups of logs over time.
|
||||||
|
//! - Heartbeat: helps us track version distribution of the CLI and critical "failures on launch" useful during new version rollouts.
|
||||||
|
//! - Rollups: helps us track performance and issues over time, as well as usage of various commands.
|
||||||
|
//!
|
||||||
|
//! If you don't run the CLI, then we won't send any data. Rollups are not done in background processes.
|
||||||
|
//!
|
||||||
|
//! We don't collect any PII, but we do collect three "controversial" pieces of data:
|
||||||
|
//! - the target triple of your system (OS, arch, etc)
|
||||||
|
//! - a session ID which is a random number generated on each run
|
||||||
|
//! - a distinct ID per `.dx` installation which is a random number generated on initial run.
|
||||||
|
//!
|
||||||
|
//! The distinct ID is used to track the same installation over time, but it is not tied to any user
|
||||||
|
//! account or PII. Since `dx` doesn't have any accounts or authentication mechanism, this ID is used
|
||||||
|
//! as a "best effort" identifier. If you still want to participate in telemetry but don't want a
|
||||||
|
//! distinct ID, you can replace the stable_id.json file in the `.dx` directory with an empty string.
|
||||||
|
//!
|
||||||
|
//! In the CLI, you can disable this by using any of the methods:
|
||||||
|
//! - installing with the "disable-telemetry" feature flag
|
||||||
|
//! - setting TELEMETRY=false in your env
|
||||||
|
//! - setting `dx config set disable-telemetry true`
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
collections::{BTreeMap, HashMap},
|
||||||
|
time::SystemTime,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An event's data, corresponding roughly to data collected from an individual trace.
|
||||||
|
///
|
||||||
|
/// This can be something like a build, bundle, translate, etc
|
||||||
|
/// We collect the phases of the build in a list of events to get a better sense of how long
|
||||||
|
/// it took.
|
||||||
|
///
|
||||||
|
/// Note that this is just the data and does not include the reporter information.
|
||||||
|
///
|
||||||
|
/// On the analytics, side, we reconstruct the trace messages into a sequence of events, using
|
||||||
|
/// the stage as a marker.
|
||||||
|
///
|
||||||
|
/// If the event contains a stack trace, it is considered a crash event and will be sent to the crash reporting service.
|
||||||
|
///
|
||||||
|
/// We store this type on disk without the reporter information or any information about the CLI.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct TelemetryEventData {
|
||||||
|
/// The name of the command that was run, e.g. "dx build", "dx bundle", "dx serve"
|
||||||
|
pub command: String,
|
||||||
|
|
||||||
|
/// The action that was taken, e.g. "build", "bundle", "cli_invoked", "cli_crashed" etc
|
||||||
|
pub action: String,
|
||||||
|
|
||||||
|
/// An additional message to include in the event, e.g. "start", "end", "error", etc
|
||||||
|
pub message: String,
|
||||||
|
|
||||||
|
/// The "name" of the error. In our case, usually" "RustError" or "RustPanic". In other languages
|
||||||
|
/// this might be the exception type. In Rust, this is usually the name of the error type. (e.g. "std::io::Error", etc)
|
||||||
|
pub error_type: Option<String>,
|
||||||
|
|
||||||
|
/// Whether the event was handled or not. Unhandled errors are the default, but some we recover from (like hotpatching issues).
|
||||||
|
pub error_handled: bool,
|
||||||
|
|
||||||
|
/// Additional values to include in the event, e.g. "duration", "enabled", etc.
|
||||||
|
pub values: HashMap<String, serde_json::Value>,
|
||||||
|
|
||||||
|
/// Timestamp of the event, in UTC, derived from the user's system time. Might not be reliable.
|
||||||
|
pub time: DateTime<Utc>,
|
||||||
|
|
||||||
|
/// The module where the event occurred, stripped of paths for privacy.
|
||||||
|
pub module: Option<String>,
|
||||||
|
|
||||||
|
/// The file or module where the event occurred, stripped of paths for privacy, relative to the monorepo root.
|
||||||
|
pub file: Option<String>,
|
||||||
|
|
||||||
|
/// The line and column where the event occurred, if applicable.
|
||||||
|
pub line: Option<u32>,
|
||||||
|
|
||||||
|
/// The column where the event occurred, if applicable.
|
||||||
|
pub column: Option<u32>,
|
||||||
|
|
||||||
|
/// The stack frames of the event, if applicable.
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub stack_frames: Vec<StackFrame>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TelemetryEventData {
|
||||||
|
pub fn new(name: impl ToString, message: impl ToString) -> Self {
|
||||||
|
Self {
|
||||||
|
command: std::env::args()
|
||||||
|
.nth(1)
|
||||||
|
.unwrap_or_else(|| "unknown".to_string()),
|
||||||
|
action: strip_paths(&name.to_string()),
|
||||||
|
message: strip_paths(&message.to_string()),
|
||||||
|
file: None,
|
||||||
|
module: None,
|
||||||
|
time: DateTime::<Utc>::from(SystemTime::now()),
|
||||||
|
values: HashMap::new(),
|
||||||
|
error_type: None,
|
||||||
|
column: None,
|
||||||
|
line: None,
|
||||||
|
stack_frames: vec![],
|
||||||
|
error_handled: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_value<K: ToString, V: serde::Serialize>(mut self, key: K, value: V) -> Self {
|
||||||
|
let mut value = serde_json::to_value(value).unwrap();
|
||||||
|
strip_paths_value(&mut value);
|
||||||
|
self.values.insert(key.to_string(), value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_module(mut self, module: impl ToString) -> Self {
|
||||||
|
self.module = Some(strip_paths(&module.to_string()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_file(mut self, file: impl ToString) -> Self {
|
||||||
|
self.file = Some(strip_paths(&file.to_string()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_line_column(mut self, line: u32, column: u32) -> Self {
|
||||||
|
self.line = Some(line);
|
||||||
|
self.column = Some(column);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_error_handled(mut self, error_handled: bool) -> Self {
|
||||||
|
self.error_handled = error_handled;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_error_type(mut self, error_type: String) -> Self {
|
||||||
|
self.error_type = Some(error_type);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_stack_frames(mut self, stack_frames: Vec<StackFrame>) -> Self {
|
||||||
|
self.stack_frames = stack_frames;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_values(mut self, fields: serde_json::Map<String, serde_json::Value>) -> Self {
|
||||||
|
for (key, value) in fields {
|
||||||
|
self = self.with_value(key, value);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_json(&self) -> serde_json::Value {
|
||||||
|
serde_json::to_value(self).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Display implementation for TelemetryEventData, such that you can use it in tracing macros with the "%" syntax.
|
||||||
|
impl std::fmt::Display for TelemetryEventData {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", serde_json::to_string(self).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A serialized stack frame, in a format that matches PostHog's stack frame format.
|
||||||
|
///
|
||||||
|
/// Read more:
|
||||||
|
/// <https://github.com/PostHog/posthog-js/blob/6e35a639a4d06804f6844cbde15adf11a069b92b/packages/node/src/extensions/error-tracking/types.ts#L55>
|
||||||
|
///
|
||||||
|
/// Supposedly, this is compatible with Sentry's stack frames as well. In the CLI we use sentry-backtrace
|
||||||
|
/// even though we don't actually use sentry.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub struct StackFrame {
|
||||||
|
pub raw_id: String,
|
||||||
|
|
||||||
|
pub mangled_name: String,
|
||||||
|
|
||||||
|
pub resolved_name: String,
|
||||||
|
|
||||||
|
pub lang: String,
|
||||||
|
|
||||||
|
pub resolved: bool,
|
||||||
|
|
||||||
|
pub platform: String,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub filename: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub function: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub module: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub lineno: Option<u64>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub colno: Option<u64>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub abs_path: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub context_line: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub pre_context: Vec<String>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub post_context: Vec<String>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub in_app: Option<bool>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub instruction_addr: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub addr_mode: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||||
|
pub vars: BTreeMap<String, serde_json::Value>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub chunk_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the CLI is compiled locally, it can contain backtraces which contain the home path with the username in it.
|
||||||
|
pub fn strip_paths(string: &str) -> String {
|
||||||
|
// Strip the home path from any paths in the backtrace
|
||||||
|
let home_dir = dirs::home_dir().unwrap_or_default();
|
||||||
|
|
||||||
|
// Strip every path between the current path and the home directory
|
||||||
|
let mut cwd = std::env::current_dir().unwrap_or_default();
|
||||||
|
let mut string = string.to_string();
|
||||||
|
loop {
|
||||||
|
string = string.replace(&*cwd.to_string_lossy(), "<stripped>");
|
||||||
|
let Some(parent) = cwd.parent() else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
cwd = parent.to_path_buf();
|
||||||
|
if cwd == home_dir {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, strip the home directory itself (in case the cwd is outside the home directory)
|
||||||
|
string.replace(&*home_dir.to_string_lossy(), "~")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strip_paths_value(value: &mut serde_json::Value) {
|
||||||
|
match value {
|
||||||
|
serde_json::Value::String(s) => *s = strip_paths(s),
|
||||||
|
serde_json::Value::Object(map) => map.values_mut().for_each(strip_paths_value),
|
||||||
|
serde_json::Value::Array(arr) => arr.iter_mut().for_each(strip_paths_value),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ dioxus-dx-wire-format = { workspace = true }
|
|||||||
wasm-split-cli = { workspace = true }
|
wasm-split-cli = { workspace = true }
|
||||||
depinfo = { workspace = true }
|
depinfo = { workspace = true }
|
||||||
subsecond-types = { workspace = true }
|
subsecond-types = { workspace = true }
|
||||||
|
dioxus-cli-telemetry = { workspace = true }
|
||||||
|
|
||||||
clap = { workspace = true, features = ["derive", "cargo"] }
|
clap = { workspace = true, features = ["derive", "cargo"] }
|
||||||
convert_case = { workspace = true }
|
convert_case = { workspace = true }
|
||||||
@@ -154,6 +155,10 @@ self_update = { version = "0.42.0", features = [
|
|||||||
] }
|
] }
|
||||||
self-replace = "1.5.0"
|
self-replace = "1.5.0"
|
||||||
cargo-config2 = { workspace = true }
|
cargo-config2 = { workspace = true }
|
||||||
|
posthog-rs = "0.3.5"
|
||||||
|
serde_json5 = "0.2.1"
|
||||||
|
fs2 = "0.4.3"
|
||||||
|
sentry-backtrace = "0.42.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
built = { version = "0.8.0", features = ["git2"] }
|
built = { version = "0.8.0", features = ["git2"] }
|
||||||
@@ -162,6 +167,7 @@ built = { version = "0.8.0", features = ["git2"] }
|
|||||||
default = []
|
default = []
|
||||||
tokio-console = ["dep:console-subscriber", "tokio/tracing"]
|
tokio-console = ["dep:console-subscriber", "tokio/tracing"]
|
||||||
no-downloads = []
|
no-downloads = []
|
||||||
|
disable-telemetry = []
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|||||||
@@ -424,7 +424,6 @@ impl AppBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::error!(?err, json = ?StructuredOutput::Error { message: err.to_string() });
|
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
BuilderUpdate::StdoutReceived { .. } => {}
|
BuilderUpdate::StdoutReceived { .. } => {}
|
||||||
|
|||||||
@@ -38,10 +38,13 @@ pub enum BuilderUpdate {
|
|||||||
message: Diagnostic,
|
message: Diagnostic,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// The build completed successfully and the artifacts are ready. The artifacts are dependent on
|
||||||
|
/// the build mode (fat vs thin vs base).
|
||||||
BuildReady {
|
BuildReady {
|
||||||
bundle: BuildArtifacts,
|
bundle: BuildArtifacts,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// The build failed. This might be because of a compilation error, or an error internal to DX.
|
||||||
BuildFailed {
|
BuildFailed {
|
||||||
err: Error,
|
err: Error,
|
||||||
},
|
},
|
||||||
@@ -62,6 +65,7 @@ pub enum BuilderUpdate {
|
|||||||
msg: String,
|
msg: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// The running app (DUT) has exited and is no longer running.
|
||||||
ProcessExited {
|
ProcessExited {
|
||||||
status: ExitStatus,
|
status: ExitStatus,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1943,7 +1943,7 @@ impl BuildRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
LinkerFlavor::Unsupported => {
|
LinkerFlavor::Unsupported => {
|
||||||
tracing::error!("Unsupported platform for fat linking");
|
tracing::error!("Unsupported platform for fat linking: {}", self.triple);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
use crate::BuildMode;
|
use crate::{cli::*, Anonymized, AppBuilder, BuildMode, BuildRequest, TargetArgs, Workspace};
|
||||||
use crate::{cli::*, AppBuilder, BuildRequest, Workspace};
|
|
||||||
|
|
||||||
use super::target::TargetArgs;
|
|
||||||
|
|
||||||
/// Build the Rust Dioxus app and all of its assets.
|
/// Build the Rust Dioxus app and all of its assets.
|
||||||
///
|
///
|
||||||
@@ -28,6 +25,16 @@ pub struct BuildArgs {
|
|||||||
pub(crate) build_arguments: TargetArgs,
|
pub(crate) build_arguments: TargetArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Anonymized for BuildArgs {
|
||||||
|
fn anonymized(&self) -> Value {
|
||||||
|
json! {{
|
||||||
|
"fullstack": self.fullstack,
|
||||||
|
"ssg": self.ssg,
|
||||||
|
"build_arguments": self.build_arguments.anonymized(),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct BuildTargets {
|
pub struct BuildTargets {
|
||||||
pub client: BuildRequest,
|
pub client: BuildRequest,
|
||||||
pub server: Option<BuildRequest>,
|
pub server: Option<BuildRequest>,
|
||||||
|
|||||||
@@ -21,9 +21,6 @@ pub(crate) enum Config {
|
|||||||
/// Create a custom html file.
|
/// Create a custom html file.
|
||||||
CustomHtml {},
|
CustomHtml {},
|
||||||
|
|
||||||
/// Print the location of the CLI log file.
|
|
||||||
LogFile {},
|
|
||||||
|
|
||||||
/// Set CLI settings.
|
/// Set CLI settings.
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Set(Setting),
|
Set(Setting),
|
||||||
@@ -39,6 +36,8 @@ pub(crate) enum Setting {
|
|||||||
AlwaysOnTop { value: BoolValue },
|
AlwaysOnTop { value: BoolValue },
|
||||||
/// Set the interval that file changes are polled on WSL for hot reloading.
|
/// Set the interval that file changes are polled on WSL for hot reloading.
|
||||||
WSLFilePollInterval { value: u16 },
|
WSLFilePollInterval { value: u16 },
|
||||||
|
/// Disable the built-in telemetry for the CLI
|
||||||
|
DisableTelemetry { value: BoolValue },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Setting {
|
impl Display for Setting {
|
||||||
@@ -48,13 +47,14 @@ impl Display for Setting {
|
|||||||
Self::AlwaysOpenBrowser { value: _ } => write!(f, "always-open-browser"),
|
Self::AlwaysOpenBrowser { value: _ } => write!(f, "always-open-browser"),
|
||||||
Self::AlwaysOnTop { value: _ } => write!(f, "always-on-top"),
|
Self::AlwaysOnTop { value: _ } => write!(f, "always-on-top"),
|
||||||
Self::WSLFilePollInterval { value: _ } => write!(f, "wsl-file-poll-interval"),
|
Self::WSLFilePollInterval { value: _ } => write!(f, "wsl-file-poll-interval"),
|
||||||
|
Self::DisableTelemetry { value: _ } => write!(f, "disable-telemetry"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clap complains if we use a bool directly and I can't find much info about it.
|
// Clap complains if we use a bool directly and I can't find much info about it.
|
||||||
// "Argument 'value` is positional and it must take a value but action is SetTrue"
|
// "Argument 'value` is positional and it must take a value but action is SetTrue"
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, clap::ValueEnum)]
|
#[derive(Debug, Clone, Copy, serde::Serialize, Deserialize, clap::ValueEnum)]
|
||||||
pub(crate) enum BoolValue {
|
pub(crate) enum BoolValue {
|
||||||
True,
|
True,
|
||||||
False,
|
False,
|
||||||
@@ -98,10 +98,6 @@ impl Config {
|
|||||||
file.write_all(content.as_bytes())?;
|
file.write_all(content.as_bytes())?;
|
||||||
tracing::info!(dx_src = ?TraceSrc::Dev, "🚩 Create custom html file done.");
|
tracing::info!(dx_src = ?TraceSrc::Dev, "🚩 Create custom html file done.");
|
||||||
}
|
}
|
||||||
Config::LogFile {} => {
|
|
||||||
let log_path = crate::logging::FileAppendLayer::log_path();
|
|
||||||
tracing::info!(dx_src = ?TraceSrc::Dev, "Log file is located at {}", log_path.display());
|
|
||||||
}
|
|
||||||
// Handle CLI settings.
|
// Handle CLI settings.
|
||||||
Config::Set(setting) => {
|
Config::Set(setting) => {
|
||||||
CliSettings::modify_settings(|settings| match setting {
|
CliSettings::modify_settings(|settings| match setting {
|
||||||
@@ -115,6 +111,9 @@ impl Config {
|
|||||||
Setting::WSLFilePollInterval { value } => {
|
Setting::WSLFilePollInterval { value } => {
|
||||||
settings.wsl_file_poll_interval = Some(value)
|
settings.wsl_file_poll_interval = Some(value)
|
||||||
}
|
}
|
||||||
|
Setting::DisableTelemetry { value } => {
|
||||||
|
settings.disable_telemetry = Some(value.into());
|
||||||
|
}
|
||||||
})?;
|
})?;
|
||||||
tracing::info!(dx_src = ?TraceSrc::Dev, "🚩 CLI setting `{setting}` has been set.");
|
tracing::info!(dx_src = ?TraceSrc::Dev, "🚩 CLI setting `{setting}` has been set.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,48 +10,48 @@ pub(crate) static DEFAULT_TEMPLATE: &str = "gh:dioxuslabs/dioxus-template";
|
|||||||
#[clap(name = "new")]
|
#[clap(name = "new")]
|
||||||
pub struct Create {
|
pub struct Create {
|
||||||
/// Create a new Dioxus project at PATH
|
/// Create a new Dioxus project at PATH
|
||||||
path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
|
||||||
/// Project name. Defaults to directory name
|
/// Project name. Defaults to directory name
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
|
||||||
/// Template path
|
/// Template path
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
template: Option<String>,
|
pub template: Option<String>,
|
||||||
|
|
||||||
/// Branch to select when using `template` from a git repository.
|
/// Branch to select when using `template` from a git repository.
|
||||||
/// Mutually exclusive with: `--revision`, `--tag`.
|
/// Mutually exclusive with: `--revision`, `--tag`.
|
||||||
#[clap(long, conflicts_with_all(["revision", "tag"]))]
|
#[clap(long, conflicts_with_all(["revision", "tag"]))]
|
||||||
branch: Option<String>,
|
pub branch: Option<String>,
|
||||||
|
|
||||||
/// A commit hash to select when using `template` from a git repository.
|
/// A commit hash to select when using `template` from a git repository.
|
||||||
/// Mutually exclusive with: `--branch`, `--tag`.
|
/// Mutually exclusive with: `--branch`, `--tag`.
|
||||||
#[clap(long, conflicts_with_all(["branch", "tag"]))]
|
#[clap(long, conflicts_with_all(["branch", "tag"]))]
|
||||||
revision: Option<String>,
|
pub revision: Option<String>,
|
||||||
|
|
||||||
/// Tag to select when using `template` from a git repository.
|
/// Tag to select when using `template` from a git repository.
|
||||||
/// Mutually exclusive with: `--branch`, `--revision`.
|
/// Mutually exclusive with: `--branch`, `--revision`.
|
||||||
#[clap(long, conflicts_with_all(["branch", "revision"]))]
|
#[clap(long, conflicts_with_all(["branch", "revision"]))]
|
||||||
tag: Option<String>,
|
pub tag: Option<String>,
|
||||||
|
|
||||||
/// Specify a sub-template within the template repository to be used as the actual template
|
/// Specify a sub-template within the template repository to be used as the actual template
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
subtemplate: Option<String>,
|
pub subtemplate: Option<String>,
|
||||||
|
|
||||||
/// Pass `<option>=<value>` for the used template (e.g., `foo=bar`)
|
/// Pass `<option>=<value>` for the used template (e.g., `foo=bar`)
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
option: Vec<String>,
|
pub option: Vec<String>,
|
||||||
|
|
||||||
/// Skip user interaction by using the default values for the used template.
|
/// Skip user interaction by using the default values for the used template.
|
||||||
/// Default values can be overridden with `--option`
|
/// Default values can be overridden with `--option`
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
yes: bool,
|
pub yes: bool,
|
||||||
|
|
||||||
/// Specify the VCS used to initialize the generated template.
|
/// Specify the VCS used to initialize the generated template.
|
||||||
/// Options: `git`, `none`.
|
/// Options: `git`, `none`.
|
||||||
#[arg(long, value_parser)]
|
#[arg(long, value_parser)]
|
||||||
vcs: Option<Vcs>,
|
pub vcs: Option<Vcs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Create {
|
impl Create {
|
||||||
@@ -99,7 +99,6 @@ impl Create {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
restore_cursor_on_sigint();
|
|
||||||
tracing::debug!(dx_src = ?TraceSrc::Dev, "Creating new project with args: {args:#?}");
|
tracing::debug!(dx_src = ?TraceSrc::Dev, "Creating new project with args: {args:#?}");
|
||||||
let path = cargo_generate::generate(args)?;
|
let path = cargo_generate::generate(args)?;
|
||||||
|
|
||||||
@@ -126,20 +125,6 @@ pub(crate) fn resolve_template_and_branch(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prevent hidden cursor if Ctrl+C is pressed when interacting
|
|
||||||
/// with cargo-generate's prompts.
|
|
||||||
///
|
|
||||||
/// See <https://github.com/DioxusLabs/dioxus/pull/2603>.
|
|
||||||
pub(crate) fn restore_cursor_on_sigint() {
|
|
||||||
ctrlc::set_handler(move || {
|
|
||||||
if let Err(err) = console::Term::stdout().show_cursor() {
|
|
||||||
eprintln!("Error showing the cursor again: {err}");
|
|
||||||
}
|
|
||||||
std::process::exit(1); // Ideally should mimic the INT signal.
|
|
||||||
})
|
|
||||||
.expect("ctrlc::set_handler");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extracts the last directory name from the `path`.
|
/// Extracts the last directory name from the `path`.
|
||||||
pub(crate) fn name_from_path(path: &Path) -> Result<String> {
|
pub(crate) fn name_from_path(path: &Path) -> Result<String> {
|
||||||
use path_absolutize::Absolutize;
|
use path_absolutize::Absolutize;
|
||||||
|
|||||||
@@ -6,48 +6,48 @@ use cargo_generate::{GenerateArgs, TemplatePath, Vcs};
|
|||||||
pub struct Init {
|
pub struct Init {
|
||||||
/// Create a new Dioxus project at PATH
|
/// Create a new Dioxus project at PATH
|
||||||
#[arg(default_value = ".")]
|
#[arg(default_value = ".")]
|
||||||
path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
|
||||||
/// Project name. Defaults to directory name
|
/// Project name. Defaults to directory name
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
|
||||||
/// Template path
|
/// Template path
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
template: Option<String>,
|
pub template: Option<String>,
|
||||||
|
|
||||||
/// Branch to select when using `template` from a git repository.
|
/// Branch to select when using `template` from a git repository.
|
||||||
/// Mutually exclusive with: `--revision`, `--tag`.
|
/// Mutually exclusive with: `--revision`, `--tag`.
|
||||||
#[clap(long, conflicts_with_all(["revision", "tag"]))]
|
#[clap(long, conflicts_with_all(["revision", "tag"]))]
|
||||||
branch: Option<String>,
|
pub branch: Option<String>,
|
||||||
|
|
||||||
/// A commit hash to select when using `template` from a git repository.
|
/// A commit hash to select when using `template` from a git repository.
|
||||||
/// Mutually exclusive with: `--branch`, `--tag`.
|
/// Mutually exclusive with: `--branch`, `--tag`.
|
||||||
#[clap(long, conflicts_with_all(["branch", "tag"]))]
|
#[clap(long, conflicts_with_all(["branch", "tag"]))]
|
||||||
revision: Option<String>,
|
pub revision: Option<String>,
|
||||||
|
|
||||||
/// Tag to select when using `template` from a git repository.
|
/// Tag to select when using `template` from a git repository.
|
||||||
/// Mutually exclusive with: `--branch`, `--revision`.
|
/// Mutually exclusive with: `--branch`, `--revision`.
|
||||||
#[clap(long, conflicts_with_all(["branch", "revision"]))]
|
#[clap(long, conflicts_with_all(["branch", "revision"]))]
|
||||||
tag: Option<String>,
|
pub tag: Option<String>,
|
||||||
|
|
||||||
/// Specify a sub-template within the template repository to be used as the actual template
|
/// Specify a sub-template within the template repository to be used as the actual template
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
subtemplate: Option<String>,
|
pub subtemplate: Option<String>,
|
||||||
|
|
||||||
/// Pass `<option>=<value>` for the used template (e.g., `foo=bar`)
|
/// Pass `<option>=<value>` for the used template (e.g., `foo=bar`)
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
option: Vec<String>,
|
pub option: Vec<String>,
|
||||||
|
|
||||||
/// Skip user interaction by using the default values for the used template.
|
/// Skip user interaction by using the default values for the used template.
|
||||||
/// Default values can be overridden with `--option`
|
/// Default values can be overridden with `--option`
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
yes: bool,
|
pub yes: bool,
|
||||||
|
|
||||||
/// Specify the VCS used to initialize the generated template.
|
/// Specify the VCS used to initialize the generated template.
|
||||||
/// Options: `git`, `none`.
|
/// Options: `git`, `none`.
|
||||||
#[arg(long, value_parser)]
|
#[arg(long, value_parser)]
|
||||||
vcs: Option<Vcs>,
|
pub vcs: Option<Vcs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Init {
|
impl Init {
|
||||||
@@ -85,7 +85,7 @@ impl Init {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
create::restore_cursor_on_sigint();
|
|
||||||
let path = cargo_generate::generate(args)?;
|
let path = cargo_generate::generate(args)?;
|
||||||
_ = create::post_create(&path, &self.vcs.unwrap_or(Vcs::Git));
|
_ = create::post_create(&path, &self.vcs.unwrap_or(Vcs::Git));
|
||||||
Ok(StructuredOutput::Success)
|
Ok(StructuredOutput::Success)
|
||||||
|
|||||||
@@ -111,9 +111,9 @@ impl LinkAction {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run_link(self) {
|
pub(crate) fn run_link(self) {
|
||||||
let link_err_file = self.link_err_file.clone();
|
let link_err_file = self.link_err_file.clone();
|
||||||
let res = self.run_link_inner().await;
|
let res = self.run_link_inner();
|
||||||
|
|
||||||
if let Err(err) = res {
|
if let Err(err) = res {
|
||||||
// If we failed to run the linker, we need to write the error to the file
|
// If we failed to run the linker, we need to write the error to the file
|
||||||
@@ -127,7 +127,7 @@ impl LinkAction {
|
|||||||
///
|
///
|
||||||
/// The file will be given by the dx-magic-link-arg env var itself, so we use
|
/// The file will be given by the dx-magic-link-arg env var itself, so we use
|
||||||
/// it both for determining if we should act as a linker and the for the file name itself.
|
/// it both for determining if we should act as a linker and the for the file name itself.
|
||||||
async fn run_link_inner(self) -> Result<()> {
|
fn run_link_inner(self) -> Result<()> {
|
||||||
let mut args: Vec<_> = std::env::args().collect();
|
let mut args: Vec<_> = std::env::args().collect();
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|||||||
@@ -22,11 +22,13 @@ pub(crate) use target::*;
|
|||||||
pub(crate) use verbosity::*;
|
pub(crate) use verbosity::*;
|
||||||
|
|
||||||
use crate::platform_override::CommandWithPlatformOverrides;
|
use crate::platform_override::CommandWithPlatformOverrides;
|
||||||
|
use crate::Anonymized;
|
||||||
use crate::{error::Result, Error, StructuredOutput};
|
use crate::{error::Result, Error, StructuredOutput};
|
||||||
use clap::builder::styling::{AnsiColor, Effects, Style, Styles};
|
use clap::builder::styling::{AnsiColor, Effects, Style, Styles};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use html_parser::Dom;
|
use html_parser::Dom;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use serde_json::{json, Value};
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
@@ -113,26 +115,6 @@ pub enum BuildTools {
|
|||||||
BuildAssets(build_assets::BuildAssets),
|
BuildAssets(build_assets::BuildAssets),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Commands {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Commands::Build(_) => write!(f, "build"),
|
|
||||||
Commands::Translate(_) => write!(f, "translate"),
|
|
||||||
Commands::Serve(_) => write!(f, "serve"),
|
|
||||||
Commands::New(_) => write!(f, "create"),
|
|
||||||
Commands::Init(_) => write!(f, "init"),
|
|
||||||
Commands::Config(_) => write!(f, "config"),
|
|
||||||
Commands::Autoformat(_) => write!(f, "fmt"),
|
|
||||||
Commands::Check(_) => write!(f, "check"),
|
|
||||||
Commands::Bundle(_) => write!(f, "bundle"),
|
|
||||||
Commands::Run(_) => write!(f, "run"),
|
|
||||||
Commands::SelfUpdate(_) => write!(f, "self-update"),
|
|
||||||
Commands::Tools(_) => write!(f, "tools"),
|
|
||||||
Commands::Doctor(_) => write!(f, "doctor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) static VERSION: LazyLock<String> = LazyLock::new(|| {
|
pub(crate) static VERSION: LazyLock<String> = LazyLock::new(|| {
|
||||||
format!(
|
format!(
|
||||||
"{} ({})",
|
"{} ({})",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
use crate::Anonymized;
|
||||||
use clap::parser::ValueSource;
|
use clap::parser::ValueSource;
|
||||||
use clap::{ArgMatches, Args, CommandFactory, FromArgMatches, Parser, Subcommand};
|
use clap::{ArgMatches, Args, CommandFactory, FromArgMatches, Parser, Subcommand};
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
/// Wraps a component with the subcommands `@server` and `@client` which will let you override the
|
/// Wraps a component with the subcommands `@server` and `@client` which will let you override the
|
||||||
/// base arguments for the client and server instances.
|
/// base arguments for the client and server instances.
|
||||||
@@ -30,6 +32,19 @@ impl<T> CommandWithPlatformOverrides<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Anonymized for CommandWithPlatformOverrides<T>
|
||||||
|
where
|
||||||
|
T: Anonymized,
|
||||||
|
{
|
||||||
|
fn anonymized(&self) -> Value {
|
||||||
|
json!({
|
||||||
|
"shared": self.shared.anonymized(),
|
||||||
|
"server": self.server.as_ref().map(|s| s.anonymized()),
|
||||||
|
"client": self.client.as_ref().map(|c| c.anonymized()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: CommandFactory + Args> Parser for CommandWithPlatformOverrides<T> {}
|
impl<T: CommandFactory + Args> Parser for CommandWithPlatformOverrides<T> {}
|
||||||
|
|
||||||
impl<T: CommandFactory + Args> CommandFactory for CommandWithPlatformOverrides<T> {
|
impl<T: CommandFactory + Args> CommandFactory for CommandWithPlatformOverrides<T> {
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{AddressArguments, BuildArgs, TraceController};
|
use crate::{AddressArguments, Anonymized, BuildArgs, TraceController};
|
||||||
use futures_util::FutureExt;
|
|
||||||
use std::sync::OnceLock;
|
|
||||||
use std::{backtrace::Backtrace, panic::AssertUnwindSafe};
|
|
||||||
|
|
||||||
/// Serve the project
|
/// Serve the project
|
||||||
///
|
///
|
||||||
/// `dx serve` takes cargo args by default with additional renderer args like `--web`, `--webview`, and `--native`:
|
/// `dx serve` takes cargo args by default with additional renderer args like `--web`, `--webview`, and `--native`:
|
||||||
@@ -99,91 +95,20 @@ impl ServeArgs {
|
|||||||
/// higher log levels
|
/// higher log levels
|
||||||
///
|
///
|
||||||
/// We also set up proper panic handling since the TUI has a tendency to corrupt the terminal.
|
/// We also set up proper panic handling since the TUI has a tendency to corrupt the terminal.
|
||||||
pub(crate) async fn serve(self) -> Result<StructuredOutput> {
|
pub(crate) async fn serve(self, tracer: &TraceController) -> Result<StructuredOutput> {
|
||||||
if std::env::var("RUST_BACKTRACE").is_err() {
|
if std::env::var("RUST_BACKTRACE").is_err() {
|
||||||
std::env::set_var("RUST_BACKTRACE", "1");
|
std::env::set_var("RUST_BACKTRACE", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SavedLocation {
|
|
||||||
file: String,
|
|
||||||
line: u32,
|
|
||||||
column: u32,
|
|
||||||
}
|
|
||||||
static BACKTRACE: OnceLock<(Backtrace, Option<SavedLocation>)> = OnceLock::new();
|
|
||||||
|
|
||||||
// We *don't* want printing here, since it'll break the tui and log ordering.
|
|
||||||
//
|
|
||||||
// We *will* re-emit the panic after we've drained the tracer, so our panic hook will simply capture the panic
|
|
||||||
// and save it.
|
|
||||||
std::panic::set_hook(Box::new(move |panic_info| {
|
|
||||||
_ = BACKTRACE.set((
|
|
||||||
Backtrace::capture(),
|
|
||||||
panic_info.location().map(|l| SavedLocation {
|
|
||||||
file: l.file().to_string(),
|
|
||||||
line: l.line(),
|
|
||||||
column: l.column(),
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}));
|
|
||||||
|
|
||||||
let interactive = self.is_interactive_tty();
|
|
||||||
|
|
||||||
// Redirect all logging the cli logger - if there's any pending after a panic, we flush it
|
// Redirect all logging the cli logger - if there's any pending after a panic, we flush it
|
||||||
let mut tracer = TraceController::redirect(interactive);
|
let is_interactive_tty = self.is_interactive_tty();
|
||||||
|
if is_interactive_tty {
|
||||||
let res = AssertUnwindSafe(crate::serve::serve_all(self, &mut tracer))
|
tracer.redirect_to_tui();
|
||||||
.catch_unwind()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Kill the screen so we don't ruin the terminal
|
|
||||||
_ = crate::serve::Output::remote_shutdown(interactive);
|
|
||||||
|
|
||||||
// And drain the tracer as regular messages. All messages will be logged (including traces)
|
|
||||||
// and then we can print the panic message
|
|
||||||
if !matches!(res, Ok(Ok(_))) {
|
|
||||||
tracer.shutdown_panic();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match res {
|
crate::serve::serve_all(self, tracer)
|
||||||
Ok(Ok(_res)) => Ok(StructuredOutput::Success),
|
.await
|
||||||
Ok(Err(e)) => Err(e),
|
.map(|_| StructuredOutput::Success)
|
||||||
Err(panic_err) => {
|
|
||||||
// And then print the panic itself.
|
|
||||||
let as_str = if let Some(p) = panic_err.downcast_ref::<String>() {
|
|
||||||
p.as_ref()
|
|
||||||
} else if let Some(p) = panic_err.downcast_ref::<&str>() {
|
|
||||||
p
|
|
||||||
} else {
|
|
||||||
"<unknown panic>"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Attempt to emulate the default panic hook
|
|
||||||
let message = BACKTRACE
|
|
||||||
.get()
|
|
||||||
.map(|(back, location)| {
|
|
||||||
let location_display = location
|
|
||||||
.as_ref()
|
|
||||||
.map(|l| format!("{}:{}:{}", l.file, l.line, l.column))
|
|
||||||
.unwrap_or_else(|| "<unknown>".to_string());
|
|
||||||
|
|
||||||
let mut backtrace_display = back.to_string();
|
|
||||||
|
|
||||||
// split at the line that ends with ___rust_try for short backtraces
|
|
||||||
if std::env::var("RUST_BACKTRACE") == Ok("1".to_string()) {
|
|
||||||
backtrace_display = backtrace_display
|
|
||||||
.split(" ___rust_try\n")
|
|
||||||
.next()
|
|
||||||
.map(|f| format!("{f} ___rust_try"))
|
|
||||||
.unwrap_or_default();
|
|
||||||
}
|
|
||||||
|
|
||||||
format!("dx serve panicked at {location_display}\n{as_str}\n{backtrace_display} ___rust_try")
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| format!("dx serve panicked: {as_str}"));
|
|
||||||
|
|
||||||
Err(anyhow::anyhow!(message))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the server is running in interactive mode. This involves checking the terminal as well
|
/// Check if the server is running in interactive mode. This involves checking the terminal as well
|
||||||
@@ -192,3 +117,31 @@ impl ServeArgs {
|
|||||||
std::io::stdout().is_terminal() && self.interactive.unwrap_or(true)
|
std::io::stdout().is_terminal() && self.interactive.unwrap_or(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Anonymized for ServeArgs {
|
||||||
|
fn anonymized(&self) -> Value {
|
||||||
|
json! {{
|
||||||
|
"address": self.address.anonymized(),
|
||||||
|
"open": self.open,
|
||||||
|
"hot_reload": self.hot_reload,
|
||||||
|
"always_on_top": self.always_on_top,
|
||||||
|
"cross_origin_policy": self.cross_origin_policy,
|
||||||
|
"wsl_file_poll_interval": self.wsl_file_poll_interval,
|
||||||
|
"interactive": self.interactive,
|
||||||
|
"hot_patch": self.hot_patch,
|
||||||
|
"watch": self.watch,
|
||||||
|
"force_sequential": self.force_sequential,
|
||||||
|
"exit_on_error": self.exit_on_error,
|
||||||
|
"platform_args": self.platform_args.anonymized(),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Anonymized for PlatformServeArgs {
|
||||||
|
fn anonymized(&self) -> Value {
|
||||||
|
json! {{
|
||||||
|
"targets": self.targets.anonymized(),
|
||||||
|
"args": !self.args.is_empty(),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -118,3 +118,31 @@ pub(crate) struct TargetArgs {
|
|||||||
#[clap(long, help_heading = HELP_HEADING)]
|
#[clap(long, help_heading = HELP_HEADING)]
|
||||||
pub(crate) base_path: Option<String>,
|
pub(crate) base_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Anonymized for TargetArgs {
|
||||||
|
fn anonymized(&self) -> Value {
|
||||||
|
json! {{
|
||||||
|
"target_alias": self.target_alias,
|
||||||
|
"renderer": self.renderer,
|
||||||
|
"bundle": self.bundle,
|
||||||
|
"platform": self.platform,
|
||||||
|
"release": self.release,
|
||||||
|
"package": self.package,
|
||||||
|
"bin": self.bin,
|
||||||
|
"example": self.example.is_some(),
|
||||||
|
"profile": self.profile.is_some(),
|
||||||
|
"features": !self.features.is_empty(),
|
||||||
|
"no_default_features": self.no_default_features,
|
||||||
|
"all_features": self.all_features,
|
||||||
|
"target": self.target.as_ref().map(|t| t.to_string()),
|
||||||
|
"skip_assets": self.skip_assets,
|
||||||
|
"inject_loading_scripts": self.inject_loading_scripts,
|
||||||
|
"wasm_split": self.wasm_split,
|
||||||
|
"debug_symbols": self.debug_symbols,
|
||||||
|
"device": self.device,
|
||||||
|
"base_path": self.base_path.is_some(),
|
||||||
|
"cargo_args": self.cargo_args.is_some(),
|
||||||
|
"rustc_args": self.rustc_args.is_some(),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[derive(Parser, Clone, Copy, Debug, Default)]
|
#[derive(Parser, Clone, Debug, Default)]
|
||||||
pub struct Verbosity {
|
pub struct Verbosity {
|
||||||
/// Use verbose output [default: false]
|
/// Use verbose output [default: false]
|
||||||
#[clap(long, global = true)]
|
#[clap(long, global = true)]
|
||||||
@@ -14,6 +15,10 @@ pub struct Verbosity {
|
|||||||
#[clap(long, global = true)]
|
#[clap(long, global = true)]
|
||||||
pub(crate) json_output: bool,
|
pub(crate) json_output: bool,
|
||||||
|
|
||||||
|
/// Write *all* logs to a file
|
||||||
|
#[clap(long, global = true, help_heading = "Logging Options")]
|
||||||
|
pub(crate) log_to_file: Option<PathBuf>,
|
||||||
|
|
||||||
/// Assert that `Cargo.lock` will remain unchanged
|
/// Assert that `Cargo.lock` will remain unchanged
|
||||||
#[clap(long, global = true, help_heading = "Manifest Options")]
|
#[clap(long, global = true, help_heading = "Manifest Options")]
|
||||||
pub(crate) locked: bool,
|
pub(crate) locked: bool,
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ pub struct CustomSignCommandSettings {
|
|||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, clap::ValueEnum)]
|
#[derive(Clone, Copy, Debug, clap::ValueEnum, Serialize)]
|
||||||
pub(crate) enum PackageType {
|
pub(crate) enum PackageType {
|
||||||
/// The macOS application bundle (.app).
|
/// The macOS application bundle (.app).
|
||||||
#[clap(name = "macos")]
|
#[clap(name = "macos")]
|
||||||
|
|||||||
@@ -11,3 +11,12 @@ pub(crate) struct AddressArguments {
|
|||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) addr: Option<std::net::IpAddr>,
|
pub(crate) addr: Option<std::net::IpAddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl crate::Anonymized for AddressArguments {
|
||||||
|
fn anonymized(&self) -> serde_json::Value {
|
||||||
|
serde_json::json!({
|
||||||
|
"port": self.port,
|
||||||
|
"addr": self.addr.map(|addr| if addr.is_loopback() { "loopback" } else if addr.is_unspecified() { "unspecified" } else { "other" }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,11 +8,6 @@ pub(crate) fn should_force_entropy() -> bool {
|
|||||||
std::env::var("DIOXUS_FORCE_ENTRY").is_ok()
|
std::env::var("DIOXUS_FORCE_ENTRY").is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should the CLI not download any additional tools?
|
|
||||||
pub(crate) fn no_downloads() -> bool {
|
|
||||||
std::env::var("NO_DOWNLOADS").is_ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Should we test the installs?
|
/// Should we test the installs?
|
||||||
#[allow(dead_code)] // -> used in tests only
|
#[allow(dead_code)] // -> used in tests only
|
||||||
pub(crate) fn test_installs() -> bool {
|
pub(crate) fn test_installs() -> bool {
|
||||||
|
|||||||
+1030
-171
File diff suppressed because it is too large.
Load diff
+27
-36
@@ -40,50 +40,41 @@ pub(crate) use workspace::*;
|
|||||||
async fn main() {
|
async fn main() {
|
||||||
// The CLI uses dx as a rustcwrapper in some instances (like binary patching)
|
// The CLI uses dx as a rustcwrapper in some instances (like binary patching)
|
||||||
if rustcwrapper::is_wrapping_rustc() {
|
if rustcwrapper::is_wrapping_rustc() {
|
||||||
return rustcwrapper::run_rustc().await;
|
return rustcwrapper::run_rustc();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're being ran as a linker (likely from ourselves), we want to act as a linker instead.
|
// If we're being ran as a linker (likely from ourselves), we want to act as a linker instead.
|
||||||
if let Some(link_args) = link::LinkAction::from_env() {
|
if let Some(link_args) = link::LinkAction::from_env() {
|
||||||
return link_args.run_link().await;
|
return link_args.run_link();
|
||||||
}
|
}
|
||||||
|
|
||||||
let args = TraceController::initialize();
|
// Run under the tracing collector so we can capture errors/panics.
|
||||||
let result = match args.action {
|
let result = TraceController::main(|args, tracer| async move {
|
||||||
Commands::Translate(opts) => opts.translate(),
|
match args {
|
||||||
Commands::New(opts) => opts.create().await,
|
Commands::Serve(opts) => opts.serve(&tracer).await,
|
||||||
Commands::Init(opts) => opts.init().await,
|
Commands::Translate(opts) => opts.translate(),
|
||||||
Commands::Config(opts) => opts.config().await,
|
Commands::New(opts) => opts.create().await,
|
||||||
Commands::Autoformat(opts) => opts.autoformat().await,
|
Commands::Init(opts) => opts.init().await,
|
||||||
Commands::Check(opts) => opts.check().await,
|
Commands::Config(opts) => opts.config().await,
|
||||||
Commands::Build(opts) => opts.build().await,
|
Commands::Autoformat(opts) => opts.autoformat().await,
|
||||||
Commands::Serve(opts) => opts.serve().await,
|
Commands::Check(opts) => opts.check().await,
|
||||||
Commands::Bundle(opts) => opts.bundle().await,
|
Commands::Build(opts) => opts.build().await,
|
||||||
Commands::Run(opts) => opts.run().await,
|
Commands::Bundle(opts) => opts.bundle().await,
|
||||||
Commands::SelfUpdate(opts) => opts.self_update().await,
|
Commands::Run(opts) => opts.run().await,
|
||||||
Commands::Tools(BuildTools::BuildAssets(opts)) => opts.run().await,
|
Commands::SelfUpdate(opts) => opts.self_update().await,
|
||||||
Commands::Doctor(opts) => opts.doctor().await,
|
Commands::Tools(BuildTools::BuildAssets(opts)) => opts.run().await,
|
||||||
};
|
Commands::Doctor(opts) => opts.doctor().await,
|
||||||
|
|
||||||
// Provide a structured output for third party tools that can consume the output of the CLI
|
|
||||||
match result {
|
|
||||||
Ok(output) => {
|
|
||||||
tracing::debug!(json = ?output);
|
|
||||||
}
|
}
|
||||||
Err(err) => {
|
});
|
||||||
tracing::error!(
|
|
||||||
json = ?StructuredOutput::Error {
|
|
||||||
message: format!("{err:?}"),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
eprintln!(
|
|
||||||
"{ERROR_STYLE}Failed{ERROR_STYLE:#}: {}",
|
|
||||||
crate::error::log_stacktrace(&err, 1),
|
|
||||||
ERROR_STYLE = crate::styles::ERROR_STYLE,
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// Print the structured output in JSON format for third-party tools to consume.
|
||||||
|
// Make sure we do this as the last step so you can always `tail -1` it
|
||||||
|
match result.await {
|
||||||
|
StructuredOutput::Error { message } => {
|
||||||
|
tracing::error!(json = ?StructuredOutput::Error { message });
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
output => tracing::debug!(json = ?output),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -65,13 +65,12 @@ fn has_linking_args() -> bool {
|
|||||||
/// Run rustc directly, but output the result to a file.
|
/// Run rustc directly, but output the result to a file.
|
||||||
///
|
///
|
||||||
/// <https://doc.rust-lang.org/cargo/reference/config.html#buildrustc>
|
/// <https://doc.rust-lang.org/cargo/reference/config.html#buildrustc>
|
||||||
pub async fn run_rustc() {
|
pub fn run_rustc() {
|
||||||
// If we are being asked to link, delegate to the linker action.
|
// If we are being asked to link, delegate to the linker action.
|
||||||
if has_linking_args() {
|
if has_linking_args() {
|
||||||
return crate::link::LinkAction::from_env()
|
return crate::link::LinkAction::from_env()
|
||||||
.expect("Linker action not found")
|
.expect("Linker action not found")
|
||||||
.run_link()
|
.run_link();
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let var_file: PathBuf = std::env::var(DX_RUSTC_WRAPPER_ENV_VAR)
|
let var_file: PathBuf = std::env::var(DX_RUSTC_WRAPPER_ENV_VAR)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ pub(crate) use update::*;
|
|||||||
/// - I'd love to be able to configure the CLI while it's running so we can change settings on the fly.
|
/// - I'd love to be able to configure the CLI while it's running so we can change settings on the fly.
|
||||||
/// - I want us to be able to detect a `server_fn` in the project and then upgrade from a static server
|
/// - I want us to be able to detect a `server_fn` in the project and then upgrade from a static server
|
||||||
/// to a dynamic one on the fly.
|
/// to a dynamic one on the fly.
|
||||||
pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) -> Result<()> {
|
pub(crate) async fn serve_all(args: ServeArgs, tracer: &TraceController) -> Result<()> {
|
||||||
// Load the args into a plan, resolving all tooling, build dirs, arguments, decoding the multi-target, etc
|
// Load the args into a plan, resolving all tooling, build dirs, arguments, decoding the multi-target, etc
|
||||||
let exit_on_error = args.exit_on_error;
|
let exit_on_error = args.exit_on_error;
|
||||||
let mut builder = AppServer::new(args).await?;
|
let mut builder = AppServer::new(args).await?;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
use crate::Result;
|
||||||
use crate::{
|
use crate::{
|
||||||
serve::{ansi_buffer::ansi_string_to_line, ServeUpdate, WebServer},
|
serve::{ansi_buffer::ansi_string_to_line, ServeUpdate, WebServer},
|
||||||
BuildId, BuildStage, BuilderUpdate, BundleFormat, TraceContent, TraceMsg, TraceSrc,
|
BuildId, BuildStage, BuilderUpdate, BundleFormat, TraceContent, TraceMsg, TraceSrc,
|
||||||
};
|
};
|
||||||
|
use anyhow::{anyhow, bail, Context};
|
||||||
use cargo_metadata::diagnostic::Diagnostic;
|
use cargo_metadata::diagnostic::Diagnostic;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
cursor::{Hide, Show},
|
cursor::{Hide, Show},
|
||||||
@@ -104,7 +106,7 @@ impl Output {
|
|||||||
|
|
||||||
/// Call the startup functions that might mess with the terminal settings.
|
/// Call the startup functions that might mess with the terminal settings.
|
||||||
/// This is meant to be paired with "shutdown" to restore the terminal to its original state.
|
/// This is meant to be paired with "shutdown" to restore the terminal to its original state.
|
||||||
fn startup(&mut self) -> io::Result<()> {
|
fn startup(&mut self) -> Result<()> {
|
||||||
if self.interactive {
|
if self.interactive {
|
||||||
// Check if writing the terminal is going to block infinitely.
|
// Check if writing the terminal is going to block infinitely.
|
||||||
// If it does, we should disable interactive mode. This ensures we work with programs like `bg`
|
// If it does, we should disable interactive mode. This ensures we work with programs like `bg`
|
||||||
@@ -138,7 +140,7 @@ impl Output {
|
|||||||
///
|
///
|
||||||
/// This lets us check if writing to tty is going to block forever and then recover, allowing
|
/// This lets us check if writing to tty is going to block forever and then recover, allowing
|
||||||
/// interopability with programs like `bg`.
|
/// interopability with programs like `bg`.
|
||||||
fn enable_raw_mode() -> io::Result<()> {
|
fn enable_raw_mode() -> Result<()> {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
use tokio::signal::unix::{signal, SignalKind};
|
use tokio::signal::unix::{signal, SignalKind};
|
||||||
@@ -152,7 +154,7 @@ impl Output {
|
|||||||
use std::io::IsTerminal;
|
use std::io::IsTerminal;
|
||||||
|
|
||||||
if !stdout().is_terminal() {
|
if !stdout().is_terminal() {
|
||||||
return io::Result::Err(io::Error::other("Not a terminal"));
|
bail!("Cannot enable raw mode on a non-terminal output");
|
||||||
}
|
}
|
||||||
|
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
@@ -164,7 +166,7 @@ impl Output {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn remote_shutdown(interactive: bool) -> io::Result<()> {
|
pub(crate) fn remote_shutdown(interactive: bool) -> Result<()> {
|
||||||
if interactive && crossterm::terminal::is_raw_mode_enabled().unwrap_or(true) {
|
if interactive && crossterm::terminal::is_raw_mode_enabled().unwrap_or(true) {
|
||||||
stdout()
|
stdout()
|
||||||
.execute(Show)?
|
.execute(Show)?
|
||||||
@@ -213,23 +215,42 @@ impl Output {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Handle an input event, returning `true` if the event should cause the program to restart.
|
/// Handle an input event, returning `true` if the event should cause the program to restart.
|
||||||
fn handle_input(&mut self, input: Event) -> io::Result<Option<ServeUpdate>> {
|
fn handle_input(&mut self, input: Event) -> Result<Option<ServeUpdate>> {
|
||||||
// handle ctrlc
|
|
||||||
if let Event::Key(key) = input {
|
|
||||||
if let KeyCode::Char('c') = key.code {
|
|
||||||
if key.modifiers.contains(KeyModifiers::CONTROL) {
|
|
||||||
return Ok(Some(ServeUpdate::Exit { error: None }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
Event::Key(key) if key.kind == KeyEventKind::Press => self.handle_keypress(key),
|
Event::Key(key) if key.kind == KeyEventKind::Press => self.handle_keypress(key),
|
||||||
_ => Ok(Some(ServeUpdate::Redraw)),
|
_ => Ok(Some(ServeUpdate::Redraw)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_keypress(&mut self, key: KeyEvent) -> io::Result<Option<ServeUpdate>> {
|
fn handle_keypress(&mut self, key: KeyEvent) -> Result<Option<ServeUpdate>> {
|
||||||
|
// Some dev helpers for testing panic propagation and error handling. Remove this eventually.
|
||||||
|
if cfg!(debug_assertions) && std::env::var("DEBUG_PANICS").is_ok() {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
|
return Ok(Some(ServeUpdate::Exit { error: None }))
|
||||||
|
}
|
||||||
|
KeyCode::Char('Z') => panic!("z pressed so we panic -> {}", uuid::Uuid::new_v4()),
|
||||||
|
KeyCode::Char('X') => bail!("x pressed so we bail -> {}", uuid::Uuid::new_v4()),
|
||||||
|
KeyCode::Char('E') => {
|
||||||
|
Err(anyhow!(
|
||||||
|
"E pressed so we bail with context -> {}",
|
||||||
|
uuid::Uuid::new_v4()
|
||||||
|
))
|
||||||
|
.context("With a message")
|
||||||
|
.context("With a context")?;
|
||||||
|
}
|
||||||
|
KeyCode::Char('C') => {
|
||||||
|
return Ok(Some(ServeUpdate::Exit {
|
||||||
|
error: Some(anyhow!(
|
||||||
|
"C pressed, so exiting with safe error -> {}",
|
||||||
|
uuid::Uuid::new_v4()
|
||||||
|
)),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Char('r') => return Ok(Some(ServeUpdate::RequestRebuild)),
|
KeyCode::Char('r') => return Ok(Some(ServeUpdate::RequestRebuild)),
|
||||||
KeyCode::Char('o') => return Ok(Some(ServeUpdate::OpenApp)),
|
KeyCode::Char('o') => return Ok(Some(ServeUpdate::OpenApp)),
|
||||||
@@ -255,7 +276,6 @@ impl Output {
|
|||||||
id: BuildId::CLIENT,
|
id: BuildId::CLIENT,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyCode::Char('c') => {
|
KeyCode::Char('c') => {
|
||||||
stdout()
|
stdout()
|
||||||
.execute(Clear(ClearType::All))?
|
.execute(Clear(ClearType::All))?
|
||||||
@@ -394,7 +414,9 @@ impl Output {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// First, dequeue any logs that have built up from event handling
|
// First, dequeue any logs that have built up from event handling
|
||||||
_ = self.drain_logs(term);
|
while let Some(log) = self.pending_logs.pop_back() {
|
||||||
|
_ = self.render_log(term, log);
|
||||||
|
}
|
||||||
|
|
||||||
// Then, draw the frame, passing along all the state of the TUI so we can render it properly
|
// Then, draw the frame, passing along all the state of the TUI so we can render it properly
|
||||||
_ = term.draw(|frame| {
|
_ = term.draw(|frame| {
|
||||||
@@ -844,16 +866,13 @@ impl Output {
|
|||||||
/// TODO(jon): we could look into implementing scroll regions ourselves, but I think insert_before will
|
/// TODO(jon): we could look into implementing scroll regions ourselves, but I think insert_before will
|
||||||
/// land in a reasonable amount of time.
|
/// land in a reasonable amount of time.
|
||||||
#[deny(clippy::manual_saturating_arithmetic)]
|
#[deny(clippy::manual_saturating_arithmetic)]
|
||||||
fn drain_logs(
|
fn render_log(
|
||||||
&mut self,
|
&mut self,
|
||||||
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
||||||
) -> io::Result<()> {
|
log: TraceMsg,
|
||||||
|
) -> Result<()> {
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
let Some(log) = self.pending_logs.pop_back() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only show debug logs if verbose is enabled
|
// Only show debug logs if verbose is enabled
|
||||||
if log.level == Level::DEBUG && !self.verbose {
|
if log.level == Level::DEBUG && !self.verbose {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -1029,3 +1048,33 @@ impl Output {
|
|||||||
lines
|
lines
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::ops::Drop for Output {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.interactive {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a handle to the terminal with a different lifetime so we can continue to call &self methods
|
||||||
|
let owned_term = self.term.clone();
|
||||||
|
let mut term = owned_term.borrow_mut();
|
||||||
|
let Some(term) = term.as_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// First, dequeue any logs that have built up from event handling
|
||||||
|
while let Some(log) = self.pending_logs.pop_back() {
|
||||||
|
_ = self.render_log(term, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And then lets move the cursor to the bottom of the terminal
|
||||||
|
let frame_rect = term.get_frame().area();
|
||||||
|
_ = term.set_cursor_position(Position {
|
||||||
|
x: 0,
|
||||||
|
y: frame_rect.y + frame_rect.height,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tear down the TUI if it's active at this point.
|
||||||
|
_ = crate::serve::Output::remote_shutdown(self.interactive);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -143,15 +143,15 @@ pub(crate) fn proxy_to(
|
|||||||
|| uri.path().starts_with("/public/")
|
|| uri.path().starts_with("/public/")
|
||||||
|| uri.path().starts_with("/wasm/")
|
|| uri.path().starts_with("/wasm/")
|
||||||
{
|
{
|
||||||
tracing::trace!(dx_src = ?TraceSrc::Dev, "[{}] {}", res.status().as_u16(), uri);
|
tracing::trace!(dx_src = ?TraceSrc::App(crate::BundleFormat::Server), "[{}] {}", res.status().as_u16(), uri);
|
||||||
} else {
|
} else {
|
||||||
tracing::info!(dx_src = ?TraceSrc::Dev, "[{}] {}", res.status().as_u16(), uri);
|
tracing::info!(dx_src = ?TraceSrc::App(crate::BundleFormat::Server), "[{}] {}", res.status().as_u16(), uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(res.into_response())
|
Ok(res.into_response())
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::error!(dx_src = ?TraceSrc::Dev, "[{}] {}", err.status().as_u16(), uri);
|
tracing::error!(dx_src = ?TraceSrc::App(crate::BundleFormat::Server), "[{}] {}", err.status().as_u16(), uri);
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ impl AppServer {
|
|||||||
|
|
||||||
let open_browser = args
|
let open_browser = args
|
||||||
.open
|
.open
|
||||||
.unwrap_or_else(|| workspace.settings.always_open_browser.unwrap_or(true))
|
.unwrap_or_else(|| workspace.settings.always_open_browser.unwrap_or(false))
|
||||||
&& interactive;
|
&& interactive;
|
||||||
|
|
||||||
let wsl_file_poll_interval = args
|
let wsl_file_poll_interval = args
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ use std::sync::LazyLock;
|
|||||||
use std::{fs, path::PathBuf, sync::Arc};
|
use std::{fs, path::PathBuf, sync::Arc};
|
||||||
use tracing::{error, trace, warn};
|
use tracing::{error, trace, warn};
|
||||||
|
|
||||||
const GLOBAL_SETTINGS_FILE_NAME: &str = "dioxus/settings.toml";
|
|
||||||
|
|
||||||
/// Describes cli settings from project or global level.
|
/// Describes cli settings from project or global level.
|
||||||
/// The order of priority goes:
|
/// The order of priority goes:
|
||||||
/// 1. CLI Flags/Arguments
|
/// 1. CLI Flags/Arguments
|
||||||
@@ -29,6 +27,8 @@ pub(crate) struct CliSettings {
|
|||||||
pub(crate) no_downloads: Option<bool>,
|
pub(crate) no_downloads: Option<bool>,
|
||||||
/// Ignore updates for this version
|
/// Ignore updates for this version
|
||||||
pub(crate) ignore_version_update: Option<String>,
|
pub(crate) ignore_version_update: Option<String>,
|
||||||
|
/// Disable telemetry
|
||||||
|
pub(crate) disable_telemetry: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CliSettings {
|
impl CliSettings {
|
||||||
@@ -43,38 +43,37 @@ impl CliSettings {
|
|||||||
CliSettings::from_global().unwrap_or_default()
|
CliSettings::from_global().unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the path to the settings toml file.
|
||||||
|
pub(crate) fn get_settings_path() -> PathBuf {
|
||||||
|
crate::Workspace::global_settings_file()
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the current settings structure from global.
|
/// Get the current settings structure from global.
|
||||||
pub(crate) fn from_global() -> Option<Self> {
|
pub(crate) fn from_global() -> Option<Self> {
|
||||||
let Some(path) = dirs::data_local_dir() else {
|
let settings = crate::Workspace::global_settings_file();
|
||||||
warn!("failed to get local data directory, some config keys may be missing");
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
let path = path.join(GLOBAL_SETTINGS_FILE_NAME);
|
if !settings.exists() {
|
||||||
let Some(data) = fs::read_to_string(path).ok() else {
|
trace!("global settings file does not exist, returning None");
|
||||||
// We use a debug here because we expect the file to not exist.
|
|
||||||
trace!("failed to read `{}` config file", GLOBAL_SETTINGS_FILE_NAME);
|
|
||||||
return None;
|
return None;
|
||||||
};
|
|
||||||
|
|
||||||
let data = toml::from_str::<CliSettings>(&data).ok();
|
|
||||||
if data.is_none() {
|
|
||||||
warn!(
|
|
||||||
"failed to parse `{}` config file",
|
|
||||||
GLOBAL_SETTINGS_FILE_NAME
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data
|
let Some(data) = fs::read_to_string(&settings).ok() else {
|
||||||
|
warn!("failed to read global settings file");
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(data) = serde_json5::from_str::<CliSettings>(&data).ok() else {
|
||||||
|
warn!("failed to parse global settings file");
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Save the current structure to the global settings toml.
|
/// Save the current structure to the global settings toml.
|
||||||
/// This does not save to project-level settings.
|
/// This does not save to project-level settings.
|
||||||
pub(crate) fn save(&self) -> Result<()> {
|
pub(crate) fn save(&self) -> Result<()> {
|
||||||
let path = Self::get_settings_path().ok_or_else(|| {
|
let path = Self::get_settings_path();
|
||||||
error!(dx_src = ?TraceSrc::Dev, "failed to get settings path");
|
|
||||||
anyhow::anyhow!("failed to get settings path")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let data = toml::to_string_pretty(&self).map_err(|e| {
|
let data = toml::to_string_pretty(&self).map_err(|e| {
|
||||||
error!(dx_src = ?TraceSrc::Dev, ?self, "failed to parse config into toml");
|
error!(dx_src = ?TraceSrc::Dev, ?self, "failed to parse config into toml");
|
||||||
@@ -103,16 +102,6 @@ impl CliSettings {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the path to the settings toml file.
|
|
||||||
pub(crate) fn get_settings_path() -> Option<PathBuf> {
|
|
||||||
let Some(path) = dirs::data_local_dir() else {
|
|
||||||
warn!("failed to get local data directory, some config keys may be missing");
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(path.join(GLOBAL_SETTINGS_FILE_NAME))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Modify the settings toml file - doesn't change the settings for this session
|
/// Modify the settings toml file - doesn't change the settings for this session
|
||||||
pub(crate) fn modify_settings(with: impl FnOnce(&mut CliSettings)) -> Result<()> {
|
pub(crate) fn modify_settings(with: impl FnOnce(&mut CliSettings)) -> Result<()> {
|
||||||
let mut _settings = CliSettings::load();
|
let mut _settings = CliSettings::load();
|
||||||
@@ -129,12 +118,55 @@ impl CliSettings {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if crate::devcfg::no_downloads() {
|
if std::env::var("NO_DOWNLOADS").is_ok() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
CliSettings::load().no_downloads.unwrap_or_default()
|
CliSettings::load().no_downloads.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if telemetry is disabled
|
||||||
|
pub(crate) fn telemetry_disabled() -> bool {
|
||||||
|
use std::env::var;
|
||||||
|
|
||||||
|
static TELEMETRY_DISABLED: LazyLock<bool> = LazyLock::new(|| {
|
||||||
|
if cfg!(feature = "disable-telemetry") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(var("DX_TELEMETRY_ENABLED"), Ok(val) if val.eq_ignore_ascii_case("false") || val == "0")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(var("TELEMETRY"), Ok(val) if val.eq_ignore_ascii_case("false") || val == "0")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CliSettings::load().disable_telemetry.unwrap_or_default()
|
||||||
|
});
|
||||||
|
|
||||||
|
*TELEMETRY_DISABLED
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_ci() -> bool {
|
||||||
|
static CI: LazyLock<bool> = LazyLock::new(|| {
|
||||||
|
if matches!(std::env::var("CI"), Ok(val) if val.eq_ignore_ascii_case("true") || val == "1")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(std::env::var("DX_CI"), Ok(val) if val.eq_ignore_ascii_case("true") || val == "1")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
});
|
||||||
|
|
||||||
|
*CI
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_wsl_file_poll_interval() -> Option<u16> {
|
fn default_wsl_file_poll_interval() -> Option<u16> {
|
||||||
|
|||||||
@@ -534,6 +534,10 @@ impl Workspace {
|
|||||||
})
|
})
|
||||||
.to_path_buf()
|
.to_path_buf()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn global_settings_file() -> PathBuf {
|
||||||
|
Self::dioxus_data_dir().join("settings.json")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Workspace {
|
impl std::fmt::Debug for Workspace {
|
||||||
|
|||||||
Reference in new issue
Block a user