Compare commits

..

6 Commits

Author SHA1 Message Date
github-actions[bot]
9bb7e79e97 apply version updates (#14425)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-09 12:48:27 +01:00
FabianLars
d566679a99 ci: don't re-generate lockfile on prepublish 2025-11-09 12:08:57 +01:00
Kushal Meghani
3899d456d4 Address review comments (#14426)
* Address review comments

* Revert comments in `impl FromStr for ConfigValue`
2025-11-07 09:38:05 +08:00
Tony
b586ecf1f4 fix(cli): demultiply tiny skia pixels (#14416)
* fix(cli): demultiply tiny skia pixels

* Pull resize out to a function `resize_image`

* Move comments as well

* Use cow for older rust versions
2025-11-06 10:12:10 +08:00
Fabian-Lars
dd70d213cd chore(deps): update minisign to 0.8 (#14415) 2025-11-05 14:58:54 +01:00
Kushal Meghani
d06a1994e9 refactor: improve cli code readability (#14333) 2025-11-05 13:48:32 +01:00
13 changed files with 143 additions and 133 deletions

View File

@@ -27,12 +27,6 @@
"dryRunCommand": true,
"pipe": true
},
{
"command": "cargo generate-lockfile",
"dryRunCommand": true,
"runFromRoot": true,
"pipe": true
},
{
"command": "cargo audit ${ process.env.CARGO_AUDIT_OPTIONS || '' }",
"dryRunCommand": true,

29
Cargo.lock generated
View File

@@ -1319,7 +1319,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -1641,6 +1641,12 @@ dependencies = [
"syn 2.0.95",
]
[[package]]
name = "ct-codecs"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b10589d1a5e400d61f9f38f12f884cfd080ff345de8f17efda36fe0e4a02aa8"
[[package]]
name = "ctor"
version = "0.2.9"
@@ -2860,9 +2866,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi",
"wasi 0.14.2+wasi-0.2.4",
"wasm-bindgen",
]
[[package]]
@@ -4311,7 +4319,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets 0.48.5",
"windows-targets 0.52.6",
]
[[package]]
@@ -4592,11 +4600,12 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "minisign"
version = "0.7.3"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b23ef13ff1d745b1e52397daaa247e333c607f3cff96d4df2b798dc252db974b"
checksum = "e6bf96cef396a17a96f7600281aa4da9229860b7a082601b1f6db6eaa5f99ee5"
dependencies = [
"getrandom 0.2.15",
"ct-codecs",
"getrandom 0.3.3",
"rpassword",
"scrypt",
]
@@ -6976,13 +6985,13 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
[[package]]
name = "rpassword"
version = "7.3.1"
version = "7.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f"
checksum = "66d4c8b64f049c6721ec8ccec37ddfc3d641c4a7fca57e8f2a89de509c73df39"
dependencies = [
"libc",
"rtoolbox",
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -8613,7 +8622,7 @@ dependencies = [
[[package]]
name = "tauri-cli"
version = "2.9.3"
version = "2.9.4"
dependencies = [
"ar",
"axum",
@@ -10315,7 +10324,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]

View File

@@ -1,5 +1,11 @@
# Changelog
## \[2.9.4]
### Bug Fixes
- [`b586ecf1f`](https://www.github.com/tauri-apps/tauri/commit/b586ecf1f4b3b087f9aa6c4668c2c18b1b7925f4) ([#14416](https://www.github.com/tauri-apps/tauri/pull/14416) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Premultiply Alpha before Resizing which gets rid of the gray fringe around the icons for svg images.
## \[2.9.3]
### Enhancements

View File

@@ -1,6 +1,6 @@
[package]
name = "tauri-cli"
version = "2.9.3"
version = "2.9.4"
authors = ["Tauri Programme within The Commons Conservancy"]
edition = "2021"
rust-version = "1.77.2"
@@ -70,7 +70,7 @@ jsonschema = "0.33"
handlebars = "6"
include_dir = "0.7"
dirs = "6"
minisign = "=0.7.3"
minisign = "0.8"
base64 = "0.22"
ureq = { version = "3", default-features = false, features = ["gzip"] }
os_info = "3"

View File

@@ -1767,17 +1767,6 @@
}
]
},
"Referer": {
"description": "TODO: docs",
"anyOf": [
{
"$ref": "#/definitions/HeaderSource"
},
{
"type": "null"
}
]
},
"Tauri-Custom-Header": {
"description": "A custom header field Tauri-Custom-Header, don't use it.\n Remember to set Access-Control-Expose-Headers accordingly\n\n **NOT INTENDED FOR PRODUCTION USE**",
"anyOf": [

View File

@@ -1,6 +1,6 @@
{
"cli.js": {
"version": "2.9.3",
"version": "2.9.4",
"node": ">= 10.0.0"
},
"tauri": "2.9.2",

View File

@@ -203,7 +203,7 @@ where
mod tests {
const PRIVATE_KEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5dkpDN09RZm5GeVAzc2RuYlNzWVVJelJRQnNIV2JUcGVXZUplWXZXYXpqUUFBQkFBQUFBQUFBQUFBQUlBQUFBQTZrN2RnWGh5dURxSzZiL1ZQSDdNcktiaHRxczQwMXdQelRHbjRNcGVlY1BLMTBxR2dpa3I3dDE1UTVDRDE4MXR4WlQwa1BQaXdxKy9UU2J2QmVSNXhOQWFDeG1GSVllbUNpTGJQRkhhTnROR3I5RmdUZi90OGtvaGhJS1ZTcjdZU0NyYzhQWlQ5cGM9Cg==";
// we use minisign=0.7.3 to prevent a breaking change
// minisign >=0.7.4,<0.8.0 couldn't handle empty passwords.
#[test]
fn empty_password_is_valid() {
let path = std::env::temp_dir().join("minisign-password-text.txt");

View File

@@ -9,6 +9,7 @@ use crate::{
};
use std::{
borrow::Cow,
collections::HashMap,
fs::{create_dir_all, File},
io::{BufWriter, Write},
@@ -124,7 +125,7 @@ impl Source {
}
}
fn resize_exact(&self, size: u32) -> Result<DynamicImage> {
fn resize_exact(&self, size: u32) -> DynamicImage {
match self {
Self::Svg(svg) => {
let mut pixmap = tiny_skia::Pixmap::new(size, size).unwrap();
@@ -134,39 +135,49 @@ impl Source {
tiny_skia::Transform::from_scale(scale, scale),
&mut pixmap.as_mut(),
);
let img_buffer = ImageBuffer::from_raw(size, size, pixmap.take()).unwrap();
Ok(DynamicImage::ImageRgba8(img_buffer))
// Switch to use `Pixmap::take_demultiplied` in the future when it's published
// https://github.com/linebender/tiny-skia/blob/624257c0feb394bf6c4d0d688f8ea8030aae320f/src/pixmap.rs#L266
let img_buffer = ImageBuffer::from_par_fn(size, size, |x, y| {
let pixel = pixmap.pixel(x, y).unwrap().demultiply();
Rgba([pixel.red(), pixel.green(), pixel.blue(), pixel.alpha()])
});
DynamicImage::ImageRgba8(img_buffer)
}
Self::DynamicImage(image) => {
// `image` does not use premultiplied alpha in resize, so we do it manually here,
// see https://github.com/image-rs/image/issues/1655
//
// image.resize_exact(size, size, FilterType::Lanczos3)
// Premultiply alpha
let premultiplied_image =
ImageBuffer::from_par_fn(image.width(), image.height(), |x, y| {
let mut pixel = image.get_pixel(x, y);
let alpha = pixel.0[3] as f32 / u8::MAX as f32;
pixel.apply_without_alpha(|channel_value| (channel_value as f32 * alpha) as u8);
pixel
});
let mut resized =
image::imageops::resize(&premultiplied_image, size, size, FilterType::Lanczos3);
// Unmultiply alpha
resized.par_pixels_mut().for_each(|pixel| {
let alpha = pixel.0[3] as f32 / u8::MAX as f32;
pixel.apply_without_alpha(|channel_value| (channel_value as f32 / alpha) as u8);
});
Ok(DynamicImage::ImageRgba8(resized))
resize_image(image, size, size)
}
}
}
}
// `image` does not use premultiplied alpha in resize, so we do it manually here,
// see https://github.com/image-rs/image/issues/1655
fn resize_image(image: &DynamicImage, new_width: u32, new_height: u32) -> DynamicImage {
// Premultiply alpha
let premultiplied_image = ImageBuffer::from_par_fn(image.width(), image.height(), |x, y| {
let mut pixel = image.get_pixel(x, y);
let alpha = pixel.0[3] as f32 / u8::MAX as f32;
pixel.apply_without_alpha(|channel_value| (channel_value as f32 * alpha) as u8);
pixel
});
let mut resized = image::imageops::resize(
&premultiplied_image,
new_width,
new_height,
FilterType::Lanczos3,
);
// Demultiply alpha
resized.par_pixels_mut().for_each(|pixel| {
let alpha = pixel.0[3] as f32 / u8::MAX as f32;
pixel.apply_without_alpha(|channel_value| (channel_value as f32 / alpha) as u8);
});
DynamicImage::ImageRgba8(resized)
}
fn read_source(path: PathBuf) -> Result<Source> {
if let Some(extension) = path.extension() {
if extension == "svg" {
@@ -183,7 +194,7 @@ fn read_source(path: PathBuf) -> Result<Source> {
..Default::default()
};
let svg_data = std::fs::read(&path).unwrap();
let svg_data = std::fs::read(&path).fs_context("Failed to read source icon", &path)?;
usvg::Tree::from_data(&svg_data, &opt).unwrap()
};
@@ -329,7 +340,7 @@ fn icns(source: &Source, out_dir: &Path) -> Result<()> {
let size = entry.size;
let mut buf = Vec::new();
let image = source.resize_exact(size)?;
let image = source.resize_exact(size);
write_png(image.as_bytes(), &mut buf, size).context("failed to write output file")?;
@@ -364,7 +375,7 @@ fn ico(source: &Source, out_dir: &Path) -> Result<()> {
let mut frames = Vec::new();
for size in [32, 16, 24, 48, 64, 256] {
let image = source.resize_exact(size)?;
let image = source.resize_exact(size);
// Only the 256px layer can be compressed according to the ico specs.
if size == 256 {
@@ -795,7 +806,7 @@ fn resize_png(
bg: Option<Background>,
scale_percent: Option<f32>,
) -> Result<DynamicImage> {
let mut image = source.resize_exact(size)?;
let mut image = source.resize_exact(size);
match bg {
Some(Background::Color(bg_color)) => {
@@ -809,7 +820,7 @@ fn resize_png(
image = bg_img.into();
}
Some(Background::Image(bg_source)) => {
let mut bg = bg_source.resize_exact(size)?;
let mut bg = bg_source.resize_exact(size);
let fg = scale_percent
.map(|scale| resize_asset(&image, size, scale))
@@ -889,9 +900,10 @@ fn content_bounds(img: &DynamicImage) -> Option<(u32, u32, u32, u32)> {
fn resize_asset(img: &DynamicImage, target_size: u32, scale_percent: f32) -> DynamicImage {
let cropped = if let Some((x, y, cw, ch)) = content_bounds(img) {
img.crop_imm(x, y, cw, ch)
// TODO: Use `&` here instead when we raise MSRV to above 1.79
Cow::Owned(img.crop_imm(x, y, cw, ch))
} else {
img.clone()
Cow::Borrowed(img)
};
let (cw, ch) = cropped.dimensions();
@@ -901,7 +913,7 @@ fn resize_asset(img: &DynamicImage, target_size: u32, scale_percent: f32) -> Dyn
let new_w = (cw as f32 * scale).round() as u32;
let new_h = (ch as f32 * scale).round() as u32;
let resized = image::imageops::resize(&cropped, new_w, new_h, image::imageops::Lanczos3);
let resized = resize_image(&cropped, new_w, new_h);
// Place on transparent square canvas
let mut canvas = ImageBuffer::from_pixel(target_size, target_size, Rgba([0, 0, 0, 0]));

View File

@@ -65,17 +65,14 @@ impl FromStr for ConfigValue {
let path = PathBuf::from(config);
let raw =
read_to_string(&path).fs_context("failed to read configuration file", path.clone())?;
match path.extension() {
Some(ext) if ext == "toml" => {
Ok(Self(::toml::from_str(&raw).with_context(|| {
format!("failed to parse config at {} as TOML", path.display())
})?))
}
Some(ext) if ext == "json5" => {
Ok(Self(::json5::from_str(&raw).with_context(|| {
format!("failed to parse config at {} as JSON5", path.display())
})?))
}
match path.extension().and_then(|ext| ext.to_str()) {
Some("toml") => Ok(Self(::toml::from_str(&raw).with_context(|| {
format!("failed to parse config at {} as TOML", path.display())
})?)),
Some("json5") => Ok(Self(::json5::from_str(&raw).with_context(|| {
format!("failed to parse config at {} as JSON5", path.display())
})?)),
// treat all other extensions as json
_ => Ok(Self(
// from tauri-utils/src/config/parse.rs:
@@ -178,6 +175,13 @@ fn format_error<I: CommandFactory>(err: clap::Error) -> clap::Error {
err.format(&mut app)
}
fn get_verbosity(cli_verbose: u8) -> u8 {
std::env::var("TAURI_CLI_VERBOSITY")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(cli_verbose)
}
/// Run the Tauri CLI with the passed arguments, exiting if an error occurs.
///
/// The passed arguments should have the binary argument(s) stripped out before being passed.
@@ -221,16 +225,12 @@ where
Ok(s) => s,
Err(e) => e.exit(),
};
let verbosity_number = std::env::var("TAURI_CLI_VERBOSITY")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(cli.verbose);
// set the verbosity level so subsequent CLI calls (xcode-script, android-studio-script) refer to it
let verbosity_number = get_verbosity(cli.verbose);
std::env::set_var("TAURI_CLI_VERBOSITY", verbosity_number.to_string());
let mut builder = Builder::from_default_env();
let init_res = builder
if let Err(err) = builder
.format_indent(Some(12))
.filter(None, verbosity_level(verbosity_number).to_level_filter())
// golbin spams an insane amount of really technical logs on the debug level so we're reducing one level
@@ -250,7 +250,6 @@ where
is_command_output = action == "stdout" || action == "stderr";
if !is_command_output {
let style = Style::new().fg_color(Some(AnsiColor::Green.into())).bold();
write!(f, "{style}{action:>12}{style:#} ")?;
}
} else {
@@ -264,15 +263,13 @@ where
if !is_command_output && log::log_enabled!(Level::Debug) {
let style = Style::new().fg_color(Some(AnsiColor::Black.into()));
write!(f, "[{style}{}{style:#}] ", record.target())?;
}
writeln!(f, "{}", record.args())
})
.try_init();
if let Err(err) = init_res {
.try_init()
{
eprintln!("Failed to attach logger: {err}");
}
@@ -305,7 +302,7 @@ fn verbosity_level(num: u8) -> Level {
match num {
0 => Level::Info,
1 => Level::Debug,
2.. => Level::Trace,
_ => Level::Trace,
}
}
@@ -332,9 +329,15 @@ impl CommandExt for Command {
self.stdin(os_pipe::dup_stdin()?);
self.stdout(os_pipe::dup_stdout()?);
self.stderr(os_pipe::dup_stderr()?);
let program = self.get_program().to_string_lossy().into_owned();
log::debug!(action = "Running"; "Command `{} {}`", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}")));
let program = self.get_program().to_string_lossy().into_owned();
let args = self
.get_args()
.map(|a| a.to_string_lossy())
.collect::<Vec<_>>()
.join(" ");
log::debug!(action = "Running"; "Command `{program} {args}`");
self.status()
}
@@ -342,8 +345,9 @@ impl CommandExt for Command {
let program = self.get_program().to_string_lossy().into_owned();
let args = self
.get_args()
.map(|arg| arg.to_string_lossy())
.fold(String::new(), |acc, arg| format!("{acc} {arg}"));
.map(|a| a.to_string_lossy())
.collect::<Vec<_>>()
.join(" ");
let cmdline = format!("{program} {args}");
log::debug!(action = "Running"; "Command `{cmdline}`");
@@ -359,16 +363,17 @@ impl CommandExt for Command {
let stdout_lines_ = stdout_lines.clone();
std::thread::spawn(move || {
let mut line = String::new();
let mut lines = stdout_lines_.lock().unwrap();
loop {
line.clear();
match stdout.read_line(&mut line) {
Ok(0) => break,
Ok(_) => {
log::debug!(action = "stdout"; "{}", line.trim_end());
lines.extend(line.as_bytes().to_vec());
if let Ok(mut lines) = stdout_lines_.lock() {
loop {
line.clear();
match stdout.read_line(&mut line) {
Ok(0) => break,
Ok(_) => {
log::debug!(action = "stdout"; "{}", line.trim_end());
lines.extend(line.as_bytes());
}
Err(_) => (),
}
Err(_) => (),
}
}
});
@@ -378,16 +383,17 @@ impl CommandExt for Command {
let stderr_lines_ = stderr_lines.clone();
std::thread::spawn(move || {
let mut line = String::new();
let mut lines = stderr_lines_.lock().unwrap();
loop {
line.clear();
match stderr.read_line(&mut line) {
Ok(0) => break,
Ok(_) => {
log::debug!(action = "stderr"; "{}", line.trim_end());
lines.extend(line.as_bytes().to_vec());
if let Ok(mut lines) = stderr_lines_.lock() {
loop {
line.clear();
match stderr.read_line(&mut line) {
Ok(0) => break,
Ok(_) => {
log::debug!(action = "stderr"; "{}", line.trim_end());
lines.extend(line.as_bytes());
}
Err(_) => (),
}
Err(_) => (),
}
}
});
@@ -423,4 +429,10 @@ mod tests {
fn verify_cli() {
Cli::command().debug_assert();
}
#[test]
fn help_output_includes_build() {
let help = Cli::command().render_help().to_string();
assert!(help.contains("Build"));
}
}

View File

@@ -1767,17 +1767,6 @@
}
]
},
"Referer": {
"description": "TODO: docs",
"anyOf": [
{
"$ref": "#/definitions/HeaderSource"
},
{
"type": "null"
}
]
},
"Tauri-Custom-Header": {
"description": "A custom header field Tauri-Custom-Header, don't use it.\n Remember to set Access-Control-Expose-Headers accordingly\n\n **NOT INTENDED FOR PRODUCTION USE**",
"anyOf": [

View File

@@ -2390,11 +2390,6 @@ impl HeaderAddition for Builder {
self = self.header("X-Content-Type-Options", value.to_string());
};
// Add the header X-Content-Type-Options, if we find a value for it
if let Some(value) = &headers.referer {
self = self.header("Referer", value.to_string());
};
// Add the header Tauri-Custom-Header, if we find a value for it
if let Some(value) = &headers.tauri_custom_header {
// Keep in mind to correctly set the Access-Control-Expose-Headers
@@ -2548,9 +2543,6 @@ pub struct HeaderConfig {
/// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options>
#[serde(rename = "X-Content-Type-Options")]
pub x_content_type_options: Option<HeaderSource>,
/// TODO: docs
#[serde(rename = "Referer")]
pub referer: Option<HeaderSource>,
/// A custom header field Tauri-Custom-Header, don't use it.
/// Remember to set Access-Control-Expose-Headers accordingly
///
@@ -2575,7 +2567,6 @@ impl HeaderConfig {
service_worker_allowed: None,
timing_allow_origin: None,
x_content_type_options: None,
referer: None,
tauri_custom_header: None,
}
}
@@ -3929,7 +3920,6 @@ mod build {
let service_worker_allowed = opt_lit(self.service_worker_allowed.as_ref());
let timing_allow_origin = opt_lit(self.timing_allow_origin.as_ref());
let x_content_type_options = opt_lit(self.x_content_type_options.as_ref());
let referer = opt_lit(self.referer.as_ref());
let tauri_custom_header = opt_lit(self.tauri_custom_header.as_ref());
literal_struct!(
@@ -3947,7 +3937,6 @@ mod build {
service_worker_allowed,
timing_allow_origin,
x_content_type_options,
referer,
tauri_custom_header
);
}

View File

@@ -1,5 +1,15 @@
# Changelog
## \[2.9.4]
### Bug Fixes
- [`b586ecf1f`](https://www.github.com/tauri-apps/tauri/commit/b586ecf1f4b3b087f9aa6c4668c2c18b1b7925f4) ([#14416](https://www.github.com/tauri-apps/tauri/pull/14416) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Premultiply Alpha before Resizing which gets rid of the gray fringe around the icons for svg images.
### Dependencies
- Upgraded to `tauri-cli@2.9.4`
## \[2.9.3]
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/cli",
"version": "2.9.3",
"version": "2.9.4",
"description": "Command line interface for building Tauri apps",
"type": "commonjs",
"funding": {