Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48f5fc410b | ||
|
|
f6725ac025 |
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "kanbangit-native",
|
"name": "kanbangit-native",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -24,5 +24,5 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "2.11.4"
|
"@tauri-apps/cli": "2.11.4"
|
||||||
},
|
},
|
||||||
"releaseNotes": "macOS updates can now be checked automatically or from the KanbanGit menu. Install releases from a drag-and-drop DMG. About KanbanGit shows the app version and build. Updates preserve your server settings and stored sessions and restart only after confirmation."
|
"releaseNotes": "About KanbanGit and Software updates now show the installed app build number correctly."
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+1
-1
@@ -1770,7 +1770,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kanbangit-native"
|
name = "kanbangit-native"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"semver",
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kanbangit-native"
|
name = "kanbangit-native"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
description = "KanbanGit native application"
|
description = "KanbanGit native application"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|||||||
@@ -285,6 +285,16 @@ pub fn context<R: tauri::Runtime>() -> tauri::Context<R> {
|
|||||||
tauri::generate_context!()
|
tauri::generate_context!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn about_metadata(version: String) -> AboutMetadata<'static> {
|
||||||
|
AboutMetadata {
|
||||||
|
name: Some(en::APP_NAME.into()),
|
||||||
|
version: Some(version),
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
short_version: Some(crate::metadata::build_number().into()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn builder() -> tauri::Builder<tauri::Wry> {
|
pub fn builder() -> tauri::Builder<tauri::Wry> {
|
||||||
tauri::Builder::default().setup(|app| {
|
tauri::Builder::default().setup(|app| {
|
||||||
let (server, phase) = match Server::load(&configuration_path(app.handle())?) {
|
let (server, phase) = match Server::load(&configuration_path(app.handle())?) {
|
||||||
@@ -301,12 +311,7 @@ pub fn builder() -> tauri::Builder<tauri::Wry> {
|
|||||||
&SubmenuBuilder::new(app, en::APP_NAME)
|
&SubmenuBuilder::new(app, en::APP_NAME)
|
||||||
.about_with_text(
|
.about_with_text(
|
||||||
en::ABOUT,
|
en::ABOUT,
|
||||||
Some(AboutMetadata {
|
Some(about_metadata(app.package_info().version.to_string())),
|
||||||
name: Some(en::APP_NAME.into()),
|
|
||||||
short_version: Some(app.package_info().version.to_string()),
|
|
||||||
version: app.config().bundle.macos.bundle_version.clone(),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
.text("check-updates", en::CHECK_UPDATES)
|
.text("check-updates", en::CHECK_UPDATES)
|
||||||
.item(&MenuItem::with_id(
|
.item(&MenuItem::with_id(
|
||||||
@@ -439,6 +444,18 @@ pub fn builder() -> tauri::Builder<tauri::Wry> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[test]
|
||||||
|
fn about_shows_app_version_followed_by_the_actual_build_number() {
|
||||||
|
let metadata = about_metadata("9.8.7".into());
|
||||||
|
assert_eq!(metadata.version.as_deref(), Some("9.8.7"));
|
||||||
|
assert_eq!(
|
||||||
|
metadata.short_version.as_deref(),
|
||||||
|
Some(crate::metadata::build_number())
|
||||||
|
);
|
||||||
|
assert_ne!(metadata.version, metadata.short_version);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn external_links_accept_only_web_urls_without_credentials() {
|
fn external_links_accept_only_web_urls_without_credentials() {
|
||||||
for input in [
|
for input in [
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ mod desktop;
|
|||||||
mod ios;
|
mod ios;
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
mod locale;
|
mod locale;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
mod metadata;
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
mod server;
|
mod server;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
use std::{path::Path, sync::LazyLock};
|
||||||
|
|
||||||
|
fn build_from_executable(executable: &Path) -> Option<String> {
|
||||||
|
let macos = executable.parent()?;
|
||||||
|
let contents = macos.parent()?;
|
||||||
|
if macos.file_name()? != "MacOS" || contents.file_name()? != "Contents" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let output = std::process::Command::new("/usr/bin/plutil")
|
||||||
|
.args(["-extract", "CFBundleVersion", "raw", "-o", "-"])
|
||||||
|
.arg(contents.join("Info.plist"))
|
||||||
|
.output()
|
||||||
|
.ok()?;
|
||||||
|
if !output.status.success() {
|
||||||
|
return Some("unknown".into());
|
||||||
|
}
|
||||||
|
let build = String::from_utf8(output.stdout).ok()?;
|
||||||
|
Some(build.trim().to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_number() -> &'static str {
|
||||||
|
static BUILD: LazyLock<String> = LazyLock::new(|| {
|
||||||
|
std::env::current_exe()
|
||||||
|
.ok()
|
||||||
|
.and_then(|executable| build_from_executable(&executable))
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
// Tauri omits macOS bundle settings from its generated runtime config.
|
||||||
|
let config: serde_json::Value =
|
||||||
|
serde_json::from_str(include_str!("../tauri.conf.json")).unwrap();
|
||||||
|
config["bundle"]["macOS"]["bundleVersion"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
.to_owned()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
&BUILD
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn installed_build_comes_from_the_app_plist_instead_of_the_app_version() {
|
||||||
|
let root = std::env::temp_dir().join(format!("kanbangit-build-{}", std::process::id()));
|
||||||
|
let contents = root.join("KanbanGit.app/Contents");
|
||||||
|
std::fs::create_dir_all(contents.join("MacOS")).unwrap();
|
||||||
|
let executable = contents.join("MacOS/kanbangit-native");
|
||||||
|
std::fs::write(contents.join("Info.plist"), r#"<?xml version="1.0" encoding="UTF-8"?><plist version="1.0"><dict><key>CFBundleShortVersionString</key><string>0.1.3</string><key>CFBundleVersion</key><string>42</string></dict></plist>"#).unwrap();
|
||||||
|
assert_eq!(build_from_executable(&executable).as_deref(), Some("42"));
|
||||||
|
std::fs::remove_file(contents.join("Info.plist")).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
build_from_executable(&executable).as_deref(),
|
||||||
|
Some("unknown")
|
||||||
|
);
|
||||||
|
std::fs::remove_dir_all(root).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unbundled_execution_uses_the_configured_build() {
|
||||||
|
assert!(build_from_executable(Path::new("/tmp/target/debug/kanbangit-native")).is_none());
|
||||||
|
let config: serde_json::Value =
|
||||||
|
serde_json::from_str(include_str!("../tauri.conf.json")).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
build_number(),
|
||||||
|
config["bundle"]["macOS"]["bundleVersion"].as_str().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -442,13 +442,7 @@ pub fn setup(app: &AppHandle) -> tauri::Result<()> {
|
|||||||
view: View {
|
view: View {
|
||||||
phase: if error.is_some() { "error" } else { "idle" },
|
phase: if error.is_some() { "error" } else { "idle" },
|
||||||
current_version: app.package_info().version.to_string(),
|
current_version: app.package_info().version.to_string(),
|
||||||
build: app
|
build: crate::metadata::build_number().into(),
|
||||||
.config()
|
|
||||||
.bundle
|
|
||||||
.macos
|
|
||||||
.bundle_version
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| app.package_info().version.to_string()),
|
|
||||||
version: None,
|
version: None,
|
||||||
notes: None,
|
notes: None,
|
||||||
downloaded: 0,
|
downloaded: 0,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "KanbanGit",
|
"productName": "KanbanGit",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"identifier": "de.kanbangit.desktop",
|
"identifier": "de.kanbangit.desktop",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../ui"
|
"frontendDist": "../ui"
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
],
|
],
|
||||||
"macOS": {
|
"macOS": {
|
||||||
"minimumSystemVersion": "14.0",
|
"minimumSystemVersion": "14.0",
|
||||||
"bundleVersion": "2",
|
"bundleVersion": "3",
|
||||||
"dmg": {
|
"dmg": {
|
||||||
"windowSize": {
|
"windowSize": {
|
||||||
"width": 660,
|
"width": 660,
|
||||||
|
|||||||
@@ -16,8 +16,16 @@ fn main() {
|
|||||||
};
|
};
|
||||||
use tauri::webview::PageLoadEvent;
|
use tauri::webview::PageLoadEvent;
|
||||||
let passed = Arc::new(AtomicBool::new(false));
|
let passed = Arc::new(AtomicBool::new(false));
|
||||||
|
let config: serde_json::Value =
|
||||||
|
serde_json::from_str(include_str!("../tauri.conf.json")).unwrap();
|
||||||
|
let expected_installed = format!(
|
||||||
|
"Installed version {} (build {})",
|
||||||
|
config["version"].as_str().unwrap(),
|
||||||
|
config["bundle"]["macOS"]["bundleVersion"].as_str().unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
let plugin = tauri::plugin::Builder::<tauri::Wry>::new("updater-smoke")
|
let plugin = tauri::plugin::Builder::<tauri::Wry>::new("updater-smoke")
|
||||||
|
.js_init_script(format!("window.expectedInstalled = {};", serde_json::to_string(&expected_installed).unwrap()))
|
||||||
.on_page_load(|window, payload| {
|
.on_page_load(|window, payload| {
|
||||||
if window.label() != "updates" || payload.event() != PageLoadEvent::Finished { return; }
|
if window.label() != "updates" || payload.event() != PageLoadEvent::Finished { return; }
|
||||||
window.eval(r#"
|
window.eval(r#"
|
||||||
@@ -26,6 +34,8 @@ fn main() {
|
|||||||
for (let i = 0; i < 100 && !window.updateStatus; i++) await new Promise(r => setTimeout(r, 50));
|
for (let i = 0; i < 100 && !window.updateStatus; i++) await new Promise(r => setTimeout(r, 50));
|
||||||
if (!window.updateStatus) throw new Error('Update UI did not load');
|
if (!window.updateStatus) throw new Error('Update UI did not load');
|
||||||
const $ = id => document.getElementById(id);
|
const $ = id => document.getElementById(id);
|
||||||
|
for (let i = 0; i < 100 && !$('installed').textContent; i++) await new Promise(r => setTimeout(r, 50));
|
||||||
|
if ($('installed').textContent !== window.expectedInstalled) throw new Error('Actual installed version/build failed: ' + $('installed').textContent);
|
||||||
const state = { phase: 'current', currentVersion: '0.1.2', build: '2', version: null, notes: null, downloaded: 0, total: null, error: null, preferences: { automatic: true, intervalHours: 6 } };
|
const state = { phase: 'current', currentVersion: '0.1.2', build: '2', version: null, notes: null, downloaded: 0, total: null, error: null, preferences: { automatic: true, intervalHours: 6 } };
|
||||||
window.updateStatus(state);
|
window.updateStatus(state);
|
||||||
if (!$('installed').textContent.includes('0.1.2 (build 2)') || !$('install').hidden || $('check').disabled) throw new Error('Current version state failed');
|
if (!$('installed').textContent.includes('0.1.2 (build 2)') || !$('install').hidden || $('check').disabled) throw new Error('Current version state failed');
|
||||||
|
|||||||
Reference in New Issue
Block a user