Pārlūkot izejas kodu

Merge branch '1.x' into dev

Lucas Nogueira 1 gadu atpakaļ
vecāks
revīzija
c6c59cf237
100 mainītis faili ar 3738 papildinājumiem un 1990 dzēšanām
  1. 15 0
      .changes/bump-1.5.md
  2. 2 2
      .changes/config.json
  3. 5 0
      .changes/nsis-german.md
  4. 7 1
      .github/workflows/audit.yml
  5. 1 4
      .github/workflows/check-generated-files.yml
  6. 148 0
      .github/workflows/covector-version-or-publish-v1.yml
  7. 4 6
      .github/workflows/covector-version-or-publish.yml
  8. 0 1
      .prettierignore
  9. 11 0
      .scripts/covector/parse-output.js
  10. 1 1
      Cargo.toml
  11. 18 0
      core/tauri-build/CHANGELOG.md
  12. 1 0
      core/tauri-build/Cargo.toml
  13. 162 28
      core/tauri-build/src/lib.rs
  14. 6 0
      core/tauri-codegen/CHANGELOG.md
  15. 1 0
      core/tauri-codegen/Cargo.toml
  16. 20 16
      core/tauri-codegen/src/context.rs
  17. 65 8
      core/tauri-config-schema/schema.json
  18. 7 0
      core/tauri-macros/CHANGELOG.md
  19. 15 0
      core/tauri-runtime-wry/CHANGELOG.md
  20. 7 0
      core/tauri-runtime-wry/src/lib.rs
  21. 10 0
      core/tauri-runtime/CHANGELOG.md
  22. 18 0
      core/tauri-utils/CHANGELOG.md
  23. 46 1
      core/tauri-utils/src/config.rs
  24. 2 4
      core/tauri-utils/src/io.rs
  25. 145 26
      core/tauri-utils/src/resources.rs
  26. 44 0
      core/tauri/CHANGELOG.md
  27. 2 1
      core/tauri/Cargo.toml
  28. 6 0
      core/tauri/src/app.rs
  29. 21 1
      core/tauri/src/lib.rs
  30. 33 9
      core/tauri/src/manager.rs
  31. 118 2
      core/tauri/src/plugin.rs
  32. 25 10
      core/tauri/src/window/mod.rs
  33. 1 0
      core/tests/app-updater/frameworks/test.framework/Headers
  34. 1 0
      core/tests/app-updater/frameworks/test.framework/Modules
  35. 1 0
      core/tests/app-updater/frameworks/test.framework/Resources
  36. 18 0
      core/tests/app-updater/frameworks/test.framework/Versions/A/Headers/test.h
  37. 6 0
      core/tests/app-updater/frameworks/test.framework/Versions/A/Modules/module.modulemap
  38. 46 0
      core/tests/app-updater/frameworks/test.framework/Versions/A/Resources/Info.plist
  39. 142 0
      core/tests/app-updater/frameworks/test.framework/Versions/A/_CodeSignature/CodeResources
  40. BIN
      core/tests/app-updater/frameworks/test.framework/Versions/A/test
  41. 1 0
      core/tests/app-updater/frameworks/test.framework/Versions/Current
  42. 0 0
      examples/api/dist/assets/index.js
  43. 209 187
      examples/api/src-tauri/Cargo.lock
  44. 14 14
      examples/multiwindow/index.html
  45. 9 9
      examples/parent-window/index.html
  46. 292 141
      examples/resources/src-tauri/Cargo.lock
  47. 1 1
      examples/resources/src-tauri/Cargo.toml
  48. 0 1
      tooling/api/.gitignore
  49. 20 0
      tooling/api/CHANGELOG.md
  50. 0 0
      tooling/api/docs/js-api.json
  51. 4 4
      tooling/api/package.json
  52. 32 0
      tooling/api/src/mocks.ts
  53. 0 18
      tooling/api/typedoc.json
  54. 12 12
      tooling/api/yarn.lock
  55. 157 52
      tooling/bench/Cargo.lock
  56. 69 0
      tooling/bundler/CHANGELOG.md
  57. 7 0
      tooling/bundler/Cargo.toml
  58. 28 1
      tooling/bundler/src/bundle.rs
  59. 11 13
      tooling/bundler/src/bundle/common.rs
  60. 12 2
      tooling/bundler/src/bundle/linux/debian.rs
  61. 81 18
      tooling/bundler/src/bundle/macos/app.rs
  62. 8 1
      tooling/bundler/src/bundle/macos/dmg.rs
  63. 189 131
      tooling/bundler/src/bundle/macos/sign.rs
  64. 49 11
      tooling/bundler/src/bundle/settings.rs
  65. 11 1
      tooling/bundler/src/bundle/updater_bundle.rs
  66. 20 24
      tooling/bundler/src/bundle/windows/msi/wix.rs
  67. 40 30
      tooling/bundler/src/bundle/windows/nsis.rs
  68. 67 15
      tooling/bundler/src/bundle/windows/sign.rs
  69. 54 39
      tooling/bundler/src/bundle/windows/templates/installer.nsi
  70. 27 0
      tooling/bundler/src/bundle/windows/templates/nsis-languages/German.nsh
  71. 21 37
      tooling/bundler/src/bundle/windows/util.rs
  72. 57 0
      tooling/cli/CHANGELOG.md
  73. 163 154
      tooling/cli/Cargo.lock
  74. 11 11
      tooling/cli/Cargo.toml
  75. 3 2
      tooling/cli/ENVIRONMENT_VARIABLES.md
  76. 3 3
      tooling/cli/metadata.json
  77. 60 0
      tooling/cli/node/CHANGELOG.md
  78. 1 1
      tooling/cli/node/README.md
  79. 1 1
      tooling/cli/node/package.json
  80. 5 5
      tooling/cli/node/tauri.js
  81. 3 3
      tooling/cli/node/test/jest/fixtures/app/dist/index.html
  82. 1 1
      tooling/cli/node/test/jest/fixtures/app/src-tauri/Cargo.toml
  83. 265 454
      tooling/cli/node/yarn.lock
  84. 65 8
      tooling/cli/schema.json
  85. 1 0
      tooling/cli/src/add.rs
  86. 4 4
      tooling/cli/src/build.rs
  87. 4 2
      tooling/cli/src/completions.rs
  88. 21 4
      tooling/cli/src/helpers/app_paths.rs
  89. 5 5
      tooling/cli/src/helpers/auto-reload.js
  90. 1 0
      tooling/cli/src/helpers/config.rs
  91. 10 0
      tooling/cli/src/helpers/npm.rs
  92. 1 1
      tooling/cli/src/helpers/web_dev_server.rs
  93. 1 2
      tooling/cli/src/icon.rs
  94. 13 35
      tooling/cli/src/info/app.rs
  95. 53 103
      tooling/cli/src/info/env_nodejs.rs
  96. 50 100
      tooling/cli/src/info/env_rust.rs
  97. 50 83
      tooling/cli/src/info/env_system.rs
  98. 18 23
      tooling/cli/src/info/ios.rs
  99. 116 34
      tooling/cli/src/info/mod.rs
  100. 126 68
      tooling/cli/src/info/packages_nodejs.rs

+ 15 - 0
.changes/bump-1.5.md

@@ -0,0 +1,15 @@
+---
+"tauri": 'patch:enhance'
+"tauri-bundler": 'patch:enhance'
+"tauri-build": 'patch:enhance'
+"tauri-codegen": 'patch:enhance'
+"tauri-macros": 'patch:enhance'
+"tauri-utils": 'patch:enhance'
+"tauri-runtime": 'patch:enhance'
+"tauri-runtime-wry": 'patch:enhance'
+"tauri-cli": 'patch:enhance'
+"@tauri-apps/cli": 'patch:enhance'
+"@tauri-apps/api": 'patch:enhance'
+---
+
+Pull changes from Tauri 1.5 release.

+ 2 - 2
.changes/config.json

@@ -1,6 +1,5 @@
 {
   "gitSiteUrl": "https://www.github.com/tauri-apps/tauri/",
-  "timeout": 3600000,
   "changeTags": {
     "feat": "New Features",
     "enhance": "Enhancements",
@@ -271,7 +270,8 @@
     },
     "tauri-driver": {
       "path": "./tooling/webdriver",
-      "manager": "rust"
+      "manager": "rust",
+      "postversion": "cargo check"
     }
   }
 }

+ 5 - 0
.changes/nsis-german.md

@@ -0,0 +1,5 @@
+---
+'tauri-bundler': 'patch:enhance'
+---
+
+Added German language support to the NSIS bundler.

+ 7 - 1
.github/workflows/audit.yml

@@ -10,6 +10,7 @@ on:
     - cron: '0 0 * * *'
   push:
     paths:
+      - '.github/workflows/audit.yml'
       - '**/Cargo.lock'
       - '**/Cargo.toml'
       - '**/package.json'
@@ -33,6 +34,11 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
-      - name: yarn audit
+      - name: audit workspace
+        run: yarn audit
+      - name: audit @tauri-apps/api
+        working-directory: tooling/api
+        run: yarn audit
+      - name: audit @tauri-apps/cli
         working-directory: tooling/cli/node
         run: yarn audit

+ 1 - 4
.github/workflows/check-generated-files.yml

@@ -32,7 +32,6 @@ jobs:
           filters: |
             api:
               - 'tooling/api/src/**'
-              - 'tooling/api/docs/js-api.json'
               - 'core/tauri/scripts/bundle.global.js'
             schema:
               - 'core/tauri-utils/src/config.rs'
@@ -50,9 +49,7 @@ jobs:
         working-directory: tooling/api
         run: yarn && yarn build
       - name: check api
-        run: |
-          git restore tooling/api/docs/js-api.json
-          ./.scripts/ci/has-diff.sh
+        run: ./.scripts/ci/has-diff.sh
 
   schema:
     runs-on: ubuntu-latest

+ 148 - 0
.github/workflows/covector-version-or-publish-v1.yml

@@ -0,0 +1,148 @@
+# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-License-Identifier: MIT
+
+name: covector version or publish
+
+on:
+  push:
+    branches:
+      - 1.x
+
+jobs:
+  run-integration-tests:
+    runs-on: ${{ matrix.platform }}
+
+    strategy:
+      fail-fast: false
+      matrix:
+        platform: [ubuntu-latest, macos-latest, windows-latest]
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+      - name: install stable
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+      - name: install Linux dependencies
+        if: matrix.platform == 'ubuntu-latest'
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y webkit2gtk-4.0 libayatana-appindicator3-dev libfuse2
+
+      - uses: Swatinem/rust-cache@v2
+        with:
+          workspaces: |
+            core -> ../target
+            tooling/cli
+
+      - name: build CLI
+        uses: actions-rs/cargo@v1
+        with:
+          command: build
+          args: --manifest-path ./tooling/cli/Cargo.toml
+
+      - name: run integration tests
+        run: cargo test --test '*' -- --ignored
+
+      - name: run CLI tests
+        timeout-minutes: 30
+        run: |
+          cd ./tooling/cli/node
+          yarn
+          yarn build
+          yarn test
+
+  version-or-publish:
+    runs-on: ubuntu-latest
+    timeout-minutes: 65
+    outputs:
+      change: ${{ steps.covector.outputs.change }}
+      commandRan: ${{ steps.covector.outputs.commandRan }}
+      successfulPublish: ${{ steps.covector.outputs.successfulPublish }}
+    needs:
+      - run-integration-tests
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+      - uses: actions/setup-node@v2
+        with:
+          node-version: 14
+          registry-url: 'https://registry.npmjs.org'
+          cache: yarn
+          cache-dependency-path: tooling/*/yarn.lock
+
+      - name: cargo login
+        run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }}
+      - name: git config
+        run: |
+          git config --global user.name "${{ github.event.pusher.name }}"
+          git config --global user.email "${{ github.event.pusher.email }}"
+
+      - name: covector version or publish (publish when no change files present)
+        uses: jbolda/covector/packages/action@covector-v0
+        id: covector
+        env:
+          NODE_AUTH_TOKEN: ${{ secrets.ORG_NPM_TOKEN }}
+          CARGO_AUDIT_OPTIONS: ${{ secrets.CARGO_AUDIT_OPTIONS }}
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+          command: 'version-or-publish'
+          createRelease: true
+
+      - name: Create Pull Request With Versions Bumped
+        if: steps.covector.outputs.commandRan == 'version'
+        uses: tauri-apps/create-pull-request@v3
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+          branch: release/version-updates-v1
+          title: Apply Version Updates From Current Changes (v1)
+          commit-message: 'apply version updates'
+          labels: 'version updates'
+          body: ${{ steps.covector.outputs.change }}
+
+      - name: Trigger doc update
+        if: |
+          steps.covector.outputs.successfulPublish == 'true' &&
+          steps.covector.outputs.packagesPublished != ''
+        uses: peter-evans/repository-dispatch@v1
+        with:
+          token: ${{ secrets.ORG_TAURI_BOT_PAT }}
+          repository: tauri-apps/tauri-docs
+          event-type: update-docs
+
+      - name: Get `@tauri-apps/cli` release id
+        id: cliReleaseId
+        if: |
+          steps.covector.outputs.successfulPublish == 'true' &&
+          contains(steps.covector.outputs.packagesPublished, '@tauri-apps/cli')
+        run: |
+          echo '${{ steps.covector.outputs }}' > output.json
+          id=$(jq '.["-tauri-apps-cli-releaseId"]' < output.json)
+          rm output.json
+          echo "cliReleaseId=$id" >> "$GITHUB_OUTPUT"
+
+      - name: Trigger `@tauri-apps/cli` publishing workflow
+        if: |
+          steps.covector.outputs.successfulPublish == 'true' &&
+          contains(steps.covector.outputs.packagesPublished, '@tauri-apps/cli')
+        uses: peter-evans/repository-dispatch@v1
+        with:
+          token: ${{ secrets.ORG_TAURI_BOT_PAT }}
+          repository: tauri-apps/tauri
+          event-type: publish-js-cli
+          client-payload: '{"releaseId": "${{ steps.cliReleaseId.outputs.cliReleaseId }}" }'
+
+      - name: Trigger `tauri-cli` publishing workflow
+        if: |
+          steps.covector.outputs.successfulPublish == 'true' &&
+          contains(steps.covector.outputs.packagesPublished, 'tauri-cli')
+        uses: peter-evans/repository-dispatch@v1
+        with:
+          token: ${{ secrets.ORG_TAURI_BOT_PAT }}
+          repository: tauri-apps/tauri
+          event-type: publish-clirs

+ 4 - 6
.github/workflows/covector-version-or-publish.yml

@@ -115,15 +115,13 @@ jobs:
           repository: tauri-apps/tauri-docs
           event-type: update-docs
 
-      - name: Get `@tauri-apps/cli` release id
-        id: cliReleaseId
+      - name: Process covector output
+        id: covectorOutput
         if: |
           steps.covector.outputs.successfulPublish == 'true' &&
           contains(steps.covector.outputs.packagesPublished, '@tauri-apps/cli')
         run: |
-          echo '${{ toJSON(steps.covector.outputs) }}' > output.json
-          id=$(jq '.["-tauri-apps-cli-releaseId"]' < output.json)
-          rm output.json
+          id=$(node .scripts/covector/parse-output.js '${{ toJSON(steps.covector.outputs) }}' "-tauri-apps-cli-releaseId")
           echo "cliReleaseId=$id" >> "$GITHUB_OUTPUT"
 
       - name: Trigger `@tauri-apps/cli` publishing workflow
@@ -135,7 +133,7 @@ jobs:
           token: ${{ secrets.ORG_TAURI_BOT_PAT }}
           repository: tauri-apps/tauri
           event-type: publish-js-cli
-          client-payload: '{"releaseId": "${{ steps.cliReleaseId.outputs.cliReleaseId }}" }'
+          client-payload: '{"releaseId": "${{ steps.covectorOutput.outputs.cliReleaseId }}" }'
 
       - name: Trigger `tauri-cli` publishing workflow
         if: |

+ 0 - 1
.prettierignore

@@ -9,5 +9,4 @@ dist
 /tooling/cli/templates
 /tooling/cli/node
 /tooling/cli/schema.json
-/tooling/api/docs/js-api.json
 /core/tauri-config-schema/schema.json

+ 11 - 0
.scripts/covector/parse-output.js

@@ -0,0 +1,11 @@
+#!/usr/bin/env node
+
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+const json = process.argv[2]
+const field = process.argv[3]
+
+const output = JSON.parse(json)
+console.log(output[field])

+ 1 - 1
Cargo.toml

@@ -1,4 +1,5 @@
 [workspace]
+resolver = "2"
 members = [
   # core
   "core/tauri",
@@ -13,7 +14,6 @@ members = [
   # integration tests
   "core/tests/restart",
 ]
-resolver = "2"
 
 exclude = [
   # examples that can be compiled with the tauri CLI

+ 18 - 0
core/tauri-build/CHANGELOG.md

@@ -86,6 +86,24 @@
 - First mobile alpha release!
   - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08
 
+## \[1.5.0]
+
+### What's Changed
+
+- [`d1e09da0`](https://www.github.com/tauri-apps/tauri/commit/d1e09da084b849b9e384fc27ed250dd17e72c7a3)([#7918](https://www.github.com/tauri-apps/tauri/pull/7918)) Bump to 1.5 due to tauri-utils dependency bump.
+
+## \[1.4.1]
+
+### Bug Fixes
+
+- [`5ecb46b3`](https://www.github.com/tauri-apps/tauri/commit/5ecb46b3410afd1b5c82494c1e0a91d5a358c41a)([#7773](https://www.github.com/tauri-apps/tauri/pull/7773)) Automatically set rpath on macOS if frameworks are bundled and copy frameworks to `src-tauri/target/Frameworks` for usage in development.
+- [`290e366a`](https://www.github.com/tauri-apps/tauri/commit/290e366ae98e9a52b1b43bfd3e285150427ebffa)([#7419](https://www.github.com/tauri-apps/tauri/pull/7419)) Correctly copy the WebView2 runtime in development when `webviewInstallMode` is used instead of `webviewFixedRuntimePath`.
+
+### Dependencies
+
+- Upgraded to `tauri-utils@1.5.0`
+- Upgraded to `tauri-codegen@1.4.1`
+
 ## \[1.4.0]
 
 ### Enhancements

+ 1 - 0
core/tauri-build/Cargo.toml

@@ -29,6 +29,7 @@ json-patch = "1.0"
 walkdir = "2"
 tauri-winres = "0.1"
 semver = "1"
+dirs-next = "2"
 
 [target."cfg(target_os = \"macos\")".dependencies]
 swift-rs = { version = "1.0.6", features = [ "build" ] }

+ 162 - 28
core/tauri-build/src/lib.rs

@@ -18,8 +18,8 @@ use cargo_toml::Manifest;
 use heck::AsShoutySnakeCase;
 
 use tauri_utils::{
-  config::Config,
-  resources::{external_binaries, resource_relpath, ResourcePaths},
+  config::{BundleResources, Config, WebviewInstallMode},
+  resources::{external_binaries, ResourcePaths},
 };
 
 use std::{
@@ -88,11 +88,117 @@ fn copy_binaries(
 
 /// Copies resources to a path.
 fn copy_resources(resources: ResourcePaths<'_>, path: &Path) -> Result<()> {
-  for src in resources {
-    let src = src?;
-    println!("cargo:rerun-if-changed={}", src.display());
-    let dest = path.join(resource_relpath(&src));
-    copy_file(&src, dest)?;
+  for resource in resources.iter() {
+    let resource = resource?;
+    println!("cargo:rerun-if-changed={}", resource.path().display());
+    copy_file(resource.path(), path.join(resource.target()))?;
+  }
+  Ok(())
+}
+
+#[cfg(unix)]
+fn symlink_dir(src: &Path, dst: &Path) -> std::io::Result<()> {
+  std::os::unix::fs::symlink(src, dst)
+}
+
+/// Makes a symbolic link to a directory.
+#[cfg(windows)]
+fn symlink_dir(src: &Path, dst: &Path) -> std::io::Result<()> {
+  std::os::windows::fs::symlink_dir(src, dst)
+}
+
+/// Makes a symbolic link to a file.
+#[cfg(unix)]
+fn symlink_file(src: &Path, dst: &Path) -> std::io::Result<()> {
+  std::os::unix::fs::symlink(src, dst)
+}
+
+/// Makes a symbolic link to a file.
+#[cfg(windows)]
+fn symlink_file(src: &Path, dst: &Path) -> std::io::Result<()> {
+  std::os::windows::fs::symlink_file(src, dst)
+}
+
+fn copy_dir(from: &Path, to: &Path) -> Result<()> {
+  for entry in walkdir::WalkDir::new(from) {
+    let entry = entry?;
+    debug_assert!(entry.path().starts_with(from));
+    let rel_path = entry.path().strip_prefix(from)?;
+    let dest_path = to.join(rel_path);
+    if entry.file_type().is_symlink() {
+      let target = std::fs::read_link(entry.path())?;
+      if entry.path().is_dir() {
+        symlink_dir(&target, &dest_path)?;
+      } else {
+        symlink_file(&target, &dest_path)?;
+      }
+    } else if entry.file_type().is_dir() {
+      std::fs::create_dir(dest_path)?;
+    } else {
+      std::fs::copy(entry.path(), dest_path)?;
+    }
+  }
+  Ok(())
+}
+
+// Copies the framework under `{src_dir}/{framework}.framework` to `{dest_dir}/{framework}.framework`.
+fn copy_framework_from(src_dir: &Path, framework: &str, dest_dir: &Path) -> Result<bool> {
+  let src_name = format!("{}.framework", framework);
+  let src_path = src_dir.join(&src_name);
+  if src_path.exists() {
+    copy_dir(&src_path, &dest_dir.join(&src_name))?;
+    Ok(true)
+  } else {
+    Ok(false)
+  }
+}
+
+// Copies the macOS application bundle frameworks to the target folder
+fn copy_frameworks(dest_dir: &Path, frameworks: &[String]) -> Result<()> {
+  std::fs::create_dir_all(dest_dir).with_context(|| {
+    format!(
+      "Failed to create frameworks output directory at {:?}",
+      dest_dir
+    )
+  })?;
+  for framework in frameworks.iter() {
+    if framework.ends_with(".framework") {
+      let src_path = PathBuf::from(framework);
+      let src_name = src_path
+        .file_name()
+        .expect("Couldn't get framework filename");
+      let dest_path = dest_dir.join(src_name);
+      copy_dir(&src_path, &dest_path)?;
+      continue;
+    } else if framework.ends_with(".dylib") {
+      let src_path = PathBuf::from(framework);
+      if !src_path.exists() {
+        return Err(anyhow::anyhow!("Library not found: {}", framework));
+      }
+      let src_name = src_path.file_name().expect("Couldn't get library filename");
+      let dest_path = dest_dir.join(src_name);
+      copy_file(&src_path, &dest_path)?;
+      continue;
+    } else if framework.contains('/') {
+      return Err(anyhow::anyhow!(
+        "Framework path should have .framework extension: {}",
+        framework
+      ));
+    }
+    if let Some(home_dir) = dirs_next::home_dir() {
+      if copy_framework_from(&home_dir.join("Library/Frameworks/"), framework, dest_dir)? {
+        continue;
+      }
+    }
+    if copy_framework_from(&PathBuf::from("/Library/Frameworks/"), framework, dest_dir)?
+      || copy_framework_from(
+        &PathBuf::from("/Network/Library/Frameworks/"),
+        framework,
+        dest_dir,
+      )?
+    {
+      continue;
+    }
   }
   Ok(())
 }
@@ -361,17 +467,47 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
   }
 
   #[allow(unused_mut, clippy::redundant_clone)]
-  let mut resources = config.tauri.bundle.resources.clone().unwrap_or_default();
+  let mut resources = config
+    .tauri
+    .bundle
+    .resources
+    .clone()
+    .unwrap_or_else(|| BundleResources::List(Vec::new()));
   if target_triple.contains("windows") {
     if let Some(fixed_webview2_runtime_path) =
-      &config.tauri.bundle.windows.webview_fixed_runtime_path
+      match &config.tauri.bundle.windows.webview_fixed_runtime_path {
+        Some(path) => Some(path),
+        None => match &config.tauri.bundle.windows.webview_install_mode {
+          WebviewInstallMode::FixedRuntime { path } => Some(path),
+          _ => None,
+        },
+      }
     {
       resources.push(fixed_webview2_runtime_path.display().to_string());
     }
   }
-  copy_resources(ResourcePaths::new(resources.as_slice(), true), target_dir)?;
+  match resources {
+    BundleResources::List(res) => {
+      copy_resources(ResourcePaths::new(res.as_slice(), true), target_dir)?
+    }
+    BundleResources::Map(map) => copy_resources(ResourcePaths::from_map(&map, true), target_dir)?,
+  }
 
   if target_triple.contains("darwin") {
+    if let Some(frameworks) = &config.tauri.bundle.macos.frameworks {
+      if !frameworks.is_empty() {
+        let frameworks_dir = target_dir.parent().unwrap().join("Frameworks");
+        let _ = std::fs::remove_dir_all(&frameworks_dir);
+        // copy frameworks to the root `target` folder (instead of `target/debug` for instance)
+        // because the rpath is set to `@executable_path/../Frameworks`.
+        copy_frameworks(&frameworks_dir, frameworks)?;
+
+        // If we have frameworks, we need to set the @rpath
+        // https://github.com/tauri-apps/tauri/issues/7710
+        println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/../Frameworks");
+      }
+    }
+
     if let Some(version) = &config.tauri.bundle.macos.minimum_system_version {
       println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET={version}");
     }
@@ -414,25 +550,23 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
             res.set_version_info(VersionInfo::FILEVERSION, version);
             res.set_version_info(VersionInfo::PRODUCTVERSION, version);
           }
-          res.set("FileVersion", version_str);
-          res.set("ProductVersion", version_str);
-        }
-        if let Some(product_name) = &config.package.product_name {
-          res.set("ProductName", product_name);
-        }
-        if let Some(short_description) = &config.tauri.bundle.short_description {
-          res.set("FileDescription", short_description);
-        }
-        if let Some(copyright) = &config.tauri.bundle.copyright {
-          res.set("LegalCopyright", copyright);
+          if let Some(product_name) = &config.package.product_name {
+            res.set("ProductName", product_name);
+          }
+          if let Some(short_description) = &config.tauri.bundle.short_description {
+            res.set("FileDescription", short_description);
+          }
+          if let Some(copyright) = &config.tauri.bundle.copyright {
+            res.set("LegalCopyright", copyright);
+          }
+          res.set_icon_with_id(&window_icon_path.display().to_string(), "32512");
+          res.compile().with_context(|| {
+            format!(
+              "failed to compile `{}` into a Windows Resource file during tauri-build",
+              window_icon_path.display()
+            )
+          })?;
         }
-        res.set_icon_with_id(&window_icon_path.display().to_string(), "32512");
-        res.compile().with_context(|| {
-          format!(
-            "failed to compile `{}` into a Windows Resource file during tauri-build",
-            window_icon_path.display()
-          )
-        })?;
       } else {
         return Err(anyhow!(format!(
           "`{}` not found; required for generating a Windows Resource file during tauri-build",

+ 6 - 0
core/tauri-codegen/CHANGELOG.md

@@ -72,6 +72,12 @@
 - First mobile alpha release!
   - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08
 
+## \[1.4.1]
+
+### Dependencies
+
+- Upgraded to `tauri-utils@1.5.0`
+
 ## \[1.4.0]
 
 ### Enhancements

+ 1 - 0
core/tauri-codegen/Cargo.toml

@@ -23,6 +23,7 @@ tauri-utils = { version = "2.0.0-alpha.9", path = "../tauri-utils", features = [
 thiserror = "1"
 walkdir = "2"
 brotli = { version = "3", optional = true, default-features = false, features = [ "std" ] }
+regex = { version = "1", optional = true }
 uuid = { version = "1", features = [ "v4" ] }
 semver = "1"
 ico = "0.3"

+ 20 - 16
core/tauri-codegen/src/context.rs

@@ -320,14 +320,11 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
       }
     }
 
-    let out_path = out_dir.join("Info.plist");
     info_plist
-      .to_file_xml(&out_path)
+      .to_file_xml(out_dir.join("Info.plist"))
       .expect("failed to write Info.plist");
-
-    let info_plist_path = out_path.display().to_string();
     quote!({
-      tauri::embed_plist::embed_info_plist!(#info_plist_path);
+      tauri::embed_plist::embed_info_plist!(concat!(std::env!("OUT_DIR"), "/Info.plist"));
     })
   } else {
     quote!(())
@@ -412,15 +409,19 @@ fn ico_icon<P: AsRef<Path>>(
   let width = entry.width();
   let height = entry.height();
 
-  let out_path = out_dir.join(path.file_name().unwrap());
+  let icon_file_name = path.file_name().unwrap();
+  let out_path = out_dir.join(icon_file_name);
   write_if_changed(&out_path, &rgba).map_err(|error| EmbeddedAssetsError::AssetWrite {
     path: path.to_owned(),
     error,
   })?;
 
-  let out_path = out_path.display().to_string();
-
-  let icon = quote!(#root::Icon::Rgba { rgba: include_bytes!(#out_path).to_vec(), width: #width, height: #height });
+  let icon_file_name = icon_file_name.to_str().unwrap();
+  let icon = quote!(#root::Icon::Rgba {
+    rgba: include_bytes!(concat!(std::env!("OUT_DIR"), "/", #icon_file_name)).to_vec(),
+    width: #width,
+    height: #height
+  });
   Ok(icon)
 }
 
@@ -436,10 +437,9 @@ fn raw_icon<P: AsRef<Path>>(out_dir: &Path, path: P) -> Result<TokenStream, Embe
     error,
   })?;
 
-  let out_path = out_path.display().to_string();
-
+  let icon_path = path.file_name().unwrap().to_str().unwrap().to_string();
   let icon = quote!(::std::option::Option::Some(
-    include_bytes!(#out_path).to_vec()
+    include_bytes!(concat!(std::env!("OUT_DIR"), "/", #icon_path)).to_vec()
   ));
   Ok(icon)
 }
@@ -471,15 +471,19 @@ fn png_icon<P: AsRef<Path>>(
   let width = reader.info().width;
   let height = reader.info().height;
 
-  let out_path = out_dir.join(path.file_name().unwrap());
+  let icon_file_name = path.file_name().unwrap();
+  let out_path = out_dir.join(icon_file_name);
   write_if_changed(&out_path, &buffer).map_err(|error| EmbeddedAssetsError::AssetWrite {
     path: path.to_owned(),
     error,
   })?;
 
-  let out_path = out_path.display().to_string();
-
-  let icon = quote!(#root::Icon::Rgba { rgba: include_bytes!(#out_path).to_vec(), width: #width, height: #height });
+  let icon_file_name = icon_file_name.to_str().unwrap();
+  let icon = quote!(#root::Icon::Rgba {
+    rgba: include_bytes!(concat!(std::env!("OUT_DIR"), "/", #icon_file_name)).to_vec(),
+    width: #width,
+    height: #height,
+  });
   Ok(icon)
 }
 

+ 65 - 8
core/tauri-config-schema/schema.json

@@ -934,13 +934,14 @@
         },
         "resources": {
           "description": "App resources to bundle. Each resource is a path to a file or directory. Glob patterns are supported.",
-          "type": [
-            "array",
-            "null"
-          ],
-          "items": {
-            "type": "string"
-          }
+          "anyOf": [
+            {
+              "$ref": "#/definitions/BundleResources"
+            },
+            {
+              "type": "null"
+            }
+          ]
         },
         "copyright": {
           "description": "A copyright string associated with your application.",
@@ -1163,6 +1164,25 @@
         }
       ]
     },
+    "BundleResources": {
+      "description": "Definition for bundle resources. Can be either a list of paths to include or a map of source to target paths.",
+      "anyOf": [
+        {
+          "description": "A list of paths to include.",
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        },
+        {
+          "description": "A map of source to target paths.",
+          "type": "object",
+          "additionalProperties": {
+            "type": "string"
+          }
+        }
+      ]
+    },
     "FileAssociation": {
       "description": "File association",
       "type": "object",
@@ -1753,6 +1773,17 @@
           "description": "Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not. By default the OS language is selected, with a fallback to the first language in the `languages` array.",
           "default": false,
           "type": "boolean"
+        },
+        "compression": {
+          "description": "Set the compression algorithm used to compress files in the installer.\n\nSee <https://nsis.sourceforge.io/Reference/SetCompressor>",
+          "anyOf": [
+            {
+              "$ref": "#/definitions/NsisCompression"
+            },
+            {
+              "type": "null"
+            }
+          ]
         }
       },
       "additionalProperties": false
@@ -1783,6 +1814,32 @@
         }
       ]
     },
+    "NsisCompression": {
+      "description": "Compression algorithms used in the NSIS installer.\n\nSee <https://nsis.sourceforge.io/Reference/SetCompressor>",
+      "oneOf": [
+        {
+          "description": "ZLIB uses the deflate algorithm, it is a quick and simple method. With the default compression level it uses about 300 KB of memory.",
+          "type": "string",
+          "enum": [
+            "zlib"
+          ]
+        },
+        {
+          "description": "BZIP2 usually gives better compression ratios than ZLIB, but it is a bit slower and uses more memory. With the default compression level it uses about 4 MB of memory.",
+          "type": "string",
+          "enum": [
+            "bzip2"
+          ]
+        },
+        {
+          "description": "LZMA (default) is a new compression method that gives very good compression ratios. The decompression speed is high (10-20 MB/s on a 2 GHz CPU), the compression speed is lower. The memory size that will be used for decompression is the dictionary size plus a few KBs, the default is 8 MB.",
+          "type": "string",
+          "enum": [
+            "lzma"
+          ]
+        }
+      ]
+    },
     "IosConfig": {
       "description": "General configuration for the iOS target.",
       "type": "object",
@@ -2019,7 +2076,7 @@
           }
         },
         "plugins": {
-          "description": "The list of plugins that are allowed in this scope.",
+          "description": "The list of plugins that are allowed in this scope. The names should be without the `tauri-plugin-` prefix, for example `\"store\"` for `tauri-plugin-store`.",
           "default": [],
           "type": "array",
           "items": {

+ 7 - 0
core/tauri-macros/CHANGELOG.md

@@ -86,6 +86,13 @@
 - First mobile alpha release!
   - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08
 
+## \[1.4.1]
+
+### Dependencies
+
+- Upgraded to `tauri-utils@1.5.0`
+- Upgraded to `tauri-codegen@1.4.1`
+
 ## \[1.4.0]
 
 ### Enhancements

+ 15 - 0
core/tauri-runtime-wry/CHANGELOG.md

@@ -121,6 +121,21 @@
 - Support `with_webview` for Android platform alowing execution of JNI code in context.
   - [8ea87e9c](https://www.github.com/tauri-apps/tauri/commit/8ea87e9c9ca8ba4c7017c8281f78aacd08f45785) feat(android): with_webview access for jni execution ([#5148](https://www.github.com/tauri-apps/tauri/pull/5148)) on 2022-09-08
 
+## \[0.14.1]
+
+### Enhancements
+
+- [`9aa34ada`](https://www.github.com/tauri-apps/tauri/commit/9aa34ada5769dbefa7dfe5f7a6288b3d20b294e4)([#7645](https://www.github.com/tauri-apps/tauri/pull/7645)) Add setting to switch to `http://<scheme>.localhost/` for custom protocols on Windows.
+
+### Bug Fixes
+
+- [`4bf1e85e`](https://www.github.com/tauri-apps/tauri/commit/4bf1e85e6bf85a7ec92d50c8465bc0588a6399d8)([#7722](https://www.github.com/tauri-apps/tauri/pull/7722)) Properly respect the `focused` option when creating the webview.
+
+### Dependencies
+
+- Upgraded to `tauri-utils@1.5.0`
+- Upgraded to `tauri-runtime@0.14.1`
+
 ## \[0.14.0]
 
 ### New Features

+ 7 - 0
core/tauri-runtime-wry/src/lib.rs

@@ -2673,6 +2673,7 @@ fn create_webview<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(
   }
 
   let is_window_transparent = window_builder.inner.window.transparent;
+  let focused = window_builder.inner.window.focused;
   let window = window_builder.inner.build(event_loop).unwrap();
 
   context.webview_id_map.insert(window.id(), window_id);
@@ -2708,6 +2709,7 @@ fn create_webview<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(
 
   let mut webview_builder = WebViewBuilder::new(window)
     .map_err(|e| Error::CreateWebview(Box::new(e)))?
+    .with_focused(focused)
     .with_url(&url)
     .unwrap() // safe to unwrap because we validate the URL beforehand
     .with_transparent(is_window_transparent)
@@ -2742,6 +2744,11 @@ fn create_webview<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(
     }
   }
 
+  #[cfg(windows)]
+  {
+    webview_builder = webview_builder.with_https_scheme(false);
+  }
+
   if let Some(handler) = ipc_handler {
     webview_builder =
       webview_builder.with_ipc_handler(create_ipc_handler(context.clone(), label.clone(), handler));

+ 10 - 0
core/tauri-runtime/CHANGELOG.md

@@ -110,6 +110,16 @@
   - Bumped due to a bump in tauri-utils.
   - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08
 
+## \[0.14.1]
+
+### Enhancements
+
+- [`9aa34ada`](https://www.github.com/tauri-apps/tauri/commit/9aa34ada5769dbefa7dfe5f7a6288b3d20b294e4)([#7645](https://www.github.com/tauri-apps/tauri/pull/7645)) Add setting to switch to `http://<scheme>.localhost/` for custom protocols on Windows.
+
+### Dependencies
+
+- Upgraded to `tauri-utils@1.5.0`
+
 ## \[0.14.0]
 
 ### New Features

+ 18 - 0
core/tauri-utils/CHANGELOG.md

@@ -90,6 +90,24 @@
 - First mobile alpha release!
   - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08
 
+## \[1.5.0]
+
+### New Features
+
+- [`4dd4893d`](https://www.github.com/tauri-apps/tauri/commit/4dd4893d7d166ac3a3b6dc2e3bd2540326352a78)([#5950](https://www.github.com/tauri-apps/tauri/pull/5950)) Allow specifying resources as a map specifying source and target paths.
+
+### Enhancements
+
+- [`9aa34ada`](https://www.github.com/tauri-apps/tauri/commit/9aa34ada5769dbefa7dfe5f7a6288b3d20b294e4)([#7645](https://www.github.com/tauri-apps/tauri/pull/7645)) Add setting to switch to `http://<scheme>.localhost/` for custom protocols on Windows.
+
+### Bug Fixes
+
+- [`a6b52e44`](https://www.github.com/tauri-apps/tauri/commit/a6b52e44f22844009e273fb0250368d7a463f095)([#6519](https://www.github.com/tauri-apps/tauri/pull/6519)) Fix `io::read_line` not including the new line character `\n`.
+
+### Security fixes
+
+- [`eeff1784`](https://www.github.com/tauri-apps/tauri/commit/eeff1784e1ffa568e4ba024e17dd611f8e086784)([#7367](https://www.github.com/tauri-apps/tauri/pull/7367)) Changed HTML implementation from unmaintained `kuchiki` to `kuchikiki`.
+
 ## \[1.4.0]
 
 ### New Features

+ 46 - 1
core/tauri-utils/src/config.rs

@@ -438,6 +438,21 @@ pub struct WixConfig {
   pub dialog_image_path: Option<PathBuf>,
 }
 
+/// Compression algorithms used in the NSIS installer.
+///
+/// See <https://nsis.sourceforge.io/Reference/SetCompressor>
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
+#[cfg_attr(feature = "schema", derive(JsonSchema))]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+pub enum NsisCompression {
+  /// ZLIB uses the deflate algorithm, it is a quick and simple method. With the default compression level it uses about 300 KB of memory.
+  Zlib,
+  /// BZIP2 usually gives better compression ratios than ZLIB, but it is a bit slower and uses more memory. With the default compression level it uses about 4 MB of memory.
+  Bzip2,
+  /// LZMA (default) is a new compression method that gives very good compression ratios. The decompression speed is high (10-20 MB/s on a 2 GHz CPU), the compression speed is lower. The memory size that will be used for decompression is the dictionary size plus a few KBs, the default is 8 MB.
+  Lzma,
+}
+
 /// Configuration for the Installer bundle using NSIS.
 #[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
 #[cfg_attr(feature = "schema", derive(JsonSchema))]
@@ -480,6 +495,10 @@ pub struct NsisConfig {
   /// By default the OS language is selected, with a fallback to the first language in the `languages` array.
   #[serde(default, alias = "display-language-selector")]
   pub display_language_selector: bool,
+  /// Set the compression algorithm used to compress files in the installer.
+  ///
+  /// See <https://nsis.sourceforge.io/Reference/SetCompressor>
+  pub compression: Option<NsisCompression>,
 }
 
 /// Install Modes for the NSIS installer.
@@ -755,6 +774,31 @@ impl Default for UpdaterConfig {
   }
 }
 
+/// Definition for bundle resources.
+/// Can be either a list of paths to include or a map of source to target paths.
+#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
+#[cfg_attr(feature = "schema", derive(JsonSchema))]
+#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
+pub enum BundleResources {
+  /// A list of paths to include.
+  List(Vec<String>),
+  /// A map of source to target paths.
+  Map(HashMap<String, String>),
+}
+
+impl BundleResources {
+  /// Adds a path to the resource collection.
+  pub fn push(&mut self, path: impl Into<String>) {
+    match self {
+      Self::List(l) => l.push(path.into()),
+      Self::Map(l) => {
+        let path = path.into();
+        l.insert(path.clone(), path);
+      }
+    }
+  }
+}
+
 /// Configuration for tauri-bundler.
 ///
 /// See more: <https://tauri.app/v1/api/config#bundleconfig>
@@ -784,7 +828,7 @@ pub struct BundleConfig {
   /// App resources to bundle.
   /// Each resource is a path to a file or directory.
   /// Glob patterns are supported.
-  pub resources: Option<Vec<String>>,
+  pub resources: Option<BundleResources>,
   /// A copyright string associated with your application.
   pub copyright: Option<String>,
   /// The application kind.
@@ -1249,6 +1293,7 @@ pub struct RemoteDomainAccessScope {
   /// The list of window labels this scope applies to.
   pub windows: Vec<String>,
   /// The list of plugins that are allowed in this scope.
+  /// The names should be without the `tauri-plugin-` prefix, for example `"store"` for `tauri-plugin-store`.
   #[serde(default)]
   pub plugins: Vec<String>,
 }

+ 2 - 4
core/tauri-utils/src/io.rs

@@ -6,7 +6,7 @@
 
 use std::io::BufRead;
 
-/// Read a line breaking in both \n and \r.
+/// Read all bytes until a newline (the `0xA` byte) or a carriage return (`\r`) is reached, and append them to the provided buffer.
 ///
 /// Adapted from <https://doc.rust-lang.org/std/io/trait.BufRead.html#method.read_line>.
 pub fn read_line<R: BufRead + ?Sized>(r: &mut R, buf: &mut Vec<u8>) -> std::io::Result<usize> {
@@ -16,6 +16,7 @@ pub fn read_line<R: BufRead + ?Sized>(r: &mut R, buf: &mut Vec<u8>) -> std::io::
       let available = match r.fill_buf() {
         Ok(n) => n,
         Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
+
         Err(e) => return Err(e),
       };
       match memchr::memchr(b'\n', available) {
@@ -40,9 +41,6 @@ pub fn read_line<R: BufRead + ?Sized>(r: &mut R, buf: &mut Vec<u8>) -> std::io::
     r.consume(used);
     read += used;
     if done || used == 0 {
-      if buf.ends_with(&[b'\n']) {
-        buf.pop();
-      }
       return Ok(read);
     }
   }

+ 145 - 26
core/tauri-utils/src/resources.rs

@@ -2,7 +2,10 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use std::path::{Component, Path, PathBuf};
+use std::{
+  collections::HashMap,
+  path::{Component, Path, PathBuf},
+};
 
 /// Given a path (absolute or relative) to a resource file, returns the
 /// relative path from the bundle resources directory where that resource
@@ -39,10 +42,58 @@ pub fn external_binaries(external_binaries: &[String], target_triple: &str) -> V
   paths
 }
 
+enum PatternIter<'a> {
+  Slice(std::slice::Iter<'a, String>),
+  Map(std::collections::hash_map::Iter<'a, String, String>),
+}
+
 /// A helper to iterate through resources.
 pub struct ResourcePaths<'a> {
+  iter: ResourcePathsIter<'a>,
+}
+
+impl<'a> ResourcePaths<'a> {
+  /// Creates a new ResourcePaths from a slice of patterns to iterate
+  pub fn new(patterns: &'a [String], allow_walk: bool) -> ResourcePaths<'a> {
+    ResourcePaths {
+      iter: ResourcePathsIter {
+        pattern_iter: PatternIter::Slice(patterns.iter()),
+        glob_iter: None,
+        walk_iter: None,
+        allow_walk,
+        current_pattern: None,
+        current_pattern_is_valid: false,
+        current_dest: None,
+      },
+    }
+  }
+
+  /// Creates a new ResourcePaths from a slice of patterns to iterate
+  pub fn from_map(patterns: &'a HashMap<String, String>, allow_walk: bool) -> ResourcePaths<'a> {
+    ResourcePaths {
+      iter: ResourcePathsIter {
+        pattern_iter: PatternIter::Map(patterns.iter()),
+        glob_iter: None,
+        walk_iter: None,
+        allow_walk,
+        current_pattern: None,
+        current_pattern_is_valid: false,
+        current_dest: None,
+      },
+    }
+  }
+
+  /// Returns the resource iterator that yields the source and target paths.
+  /// Needed when using [`Self::from_map`].
+  pub fn iter(self) -> ResourcePathsIter<'a> {
+    self.iter
+  }
+}
+
+/// Iterator of a [`ResourcePaths`].
+pub struct ResourcePathsIter<'a> {
   /// the patterns to iterate.
-  pattern_iter: std::slice::Iter<'a, String>,
+  pattern_iter: PatternIter<'a>,
   /// the glob iterator if the path from the current iteration is a glob pattern.
   glob_iter: Option<glob::Paths>,
   /// the walkdir iterator if the path from the current iteration is a directory.
@@ -50,22 +101,28 @@ pub struct ResourcePaths<'a> {
   /// whether the resource paths allows directories or not.
   allow_walk: bool,
   /// the pattern of the current iteration.
-  current_pattern: Option<String>,
+  current_pattern: Option<(String, PathBuf)>,
   /// whether the current pattern is valid or not.
   current_pattern_is_valid: bool,
+  /// Current destination path. Only set when the iterator comes from a Map.
+  current_dest: Option<PathBuf>,
 }
 
-impl<'a> ResourcePaths<'a> {
-  /// Creates a new ResourcePaths from a slice of patterns to iterate
-  pub fn new(patterns: &'a [String], allow_walk: bool) -> ResourcePaths<'a> {
-    ResourcePaths {
-      pattern_iter: patterns.iter(),
-      glob_iter: None,
-      walk_iter: None,
-      allow_walk,
-      current_pattern: None,
-      current_pattern_is_valid: false,
-    }
+/// Information for a resource.
+pub struct Resource {
+  path: PathBuf,
+  target: PathBuf,
+}
+
+impl Resource {
+  /// The path of the resource.
+  pub fn path(&self) -> &Path {
+    &self.path
+  }
+
+  /// The target location of the resource.
+  pub fn target(&self) -> &Path {
+    &self.target
   }
 }
 
@@ -73,6 +130,28 @@ impl<'a> Iterator for ResourcePaths<'a> {
   type Item = crate::Result<PathBuf>;
 
   fn next(&mut self) -> Option<crate::Result<PathBuf>> {
+    self.iter.next().map(|r| r.map(|res| res.path))
+  }
+}
+
+fn normalize(path: &Path) -> PathBuf {
+  let mut dest = PathBuf::new();
+  for component in path.components() {
+    match component {
+      Component::Prefix(_) => {}
+      Component::RootDir => dest.push("/"),
+      Component::CurDir => {}
+      Component::ParentDir => dest.push(".."),
+      Component::Normal(string) => dest.push(string),
+    }
+  }
+  dest
+}
+
+impl<'a> Iterator for ResourcePathsIter<'a> {
+  type Item = crate::Result<Resource>;
+
+  fn next(&mut self) -> Option<crate::Result<Resource>> {
     loop {
       if let Some(ref mut walk_entries) = self.walk_iter {
         if let Some(entry) = walk_entries.next() {
@@ -85,7 +164,20 @@ impl<'a> Iterator for ResourcePaths<'a> {
             continue;
           }
           self.current_pattern_is_valid = true;
-          return Some(Ok(path.to_path_buf()));
+          return Some(Ok(Resource {
+            target: if let (Some(current_dest), Some(current_pattern)) =
+              (&self.current_dest, &self.current_pattern)
+            {
+              if current_pattern.0.contains('*') {
+                current_dest.join(path.file_name().unwrap())
+              } else {
+                current_dest.join(path.strip_prefix(&current_pattern.1).unwrap())
+              }
+            } else {
+              resource_relpath(path)
+            },
+            path: path.to_path_buf(),
+          }));
         }
       }
       self.walk_iter = None;
@@ -105,24 +197,51 @@ impl<'a> Iterator for ResourcePaths<'a> {
             }
           }
           self.current_pattern_is_valid = true;
-          return Some(Ok(path));
+          return Some(Ok(Resource {
+            target: if let Some(current_dest) = &self.current_dest {
+              current_dest.join(path.file_name().unwrap())
+            } else {
+              resource_relpath(&path)
+            },
+            path,
+          }));
         } else if let Some(current_path) = &self.current_pattern {
           if !self.current_pattern_is_valid {
             self.glob_iter = None;
-            return Some(Err(crate::Error::GlobPathNotFound(current_path.clone())));
+            return Some(Err(crate::Error::GlobPathNotFound(current_path.0.clone())));
           }
         }
       }
       self.glob_iter = None;
-      if let Some(pattern) = self.pattern_iter.next() {
-        self.current_pattern = Some(pattern.to_string());
-        self.current_pattern_is_valid = false;
-        let glob = match glob::glob(pattern) {
-          Ok(glob) => glob,
-          Err(error) => return Some(Err(error.into())),
-        };
-        self.glob_iter = Some(glob);
-        continue;
+      self.current_dest = None;
+      match &mut self.pattern_iter {
+        PatternIter::Slice(iter) => {
+          if let Some(pattern) = iter.next() {
+            self.current_pattern = Some((pattern.to_string(), normalize(Path::new(pattern))));
+            self.current_pattern_is_valid = false;
+            let glob = match glob::glob(pattern) {
+              Ok(glob) => glob,
+              Err(error) => return Some(Err(error.into())),
+            };
+            self.glob_iter = Some(glob);
+            continue;
+          }
+        }
+        PatternIter::Map(iter) => {
+          if let Some((pattern, dest)) = iter.next() {
+            self.current_pattern = Some((pattern.to_string(), normalize(Path::new(pattern))));
+            self.current_pattern_is_valid = false;
+            let glob = match glob::glob(pattern) {
+              Ok(glob) => glob,
+              Err(error) => return Some(Err(error.into())),
+            };
+            self
+              .current_dest
+              .replace(resource_relpath(&PathBuf::from(dest)));
+            self.glob_iter = Some(glob);
+            continue;
+          }
+        }
       }
       return None;
     }

+ 44 - 0
core/tauri/CHANGELOG.md

@@ -339,6 +339,50 @@
 - Export types required by the `mobile_entry_point` macro.
   - [98904863](https://www.github.com/tauri-apps/tauri/commit/9890486321c9c79ccfb7c547fafee85b5c3ffa71) feat(core): add `mobile_entry_point` macro ([#4983](https://www.github.com/tauri-apps/tauri/pull/4983)) on 2022-08-21
 
+## \[1.5.2]
+
+### Bug Fixes
+
+- [`21cdbb41`](https://www.github.com/tauri-apps/tauri/commit/21cdbb41a38f465148bbeb82feb3e7886c320182)([#7982](https://www.github.com/tauri-apps/tauri/pull/7982)) Set the correct `truncate` option on `OpenOptions` so that `write_file` can completely overwrite existing files.
+
+## \[1.5.1]
+
+### Bug Fixes
+
+- [`3671edbc`](https://www.github.com/tauri-apps/tauri/commit/3671edbcff37447c95382ab4c9fd1c36a460a037)([#7937](https://www.github.com/tauri-apps/tauri/pull/7937)) Fix devtools not toggling on `ctrl+shift+i` or `cmd+alt+i` shortcuts.
+
+## \[1.5.0]
+
+### New Features
+
+- [`eeb6be54`](https://www.github.com/tauri-apps/tauri/commit/eeb6be54228f3e5463a28c68956abb06a694c010)([#7512](https://www.github.com/tauri-apps/tauri/pull/7512)) Add `tauri::Manager::emit_filter` and only serialize once when emitting to multiple windows.
+- [`6c408b73`](https://www.github.com/tauri-apps/tauri/commit/6c408b736c7aa2a0a91f0a40d45a2b7a7dedfe78)([#7269](https://www.github.com/tauri-apps/tauri/pull/7269)) Add option to specify notification sound.
+- [`fdaee9a5`](https://www.github.com/tauri-apps/tauri/commit/fdaee9a5ce988c448dd035c2050c339d275e8d15)([#7350](https://www.github.com/tauri-apps/tauri/pull/7350)) Add `tauri::plugin::Builder::register_uri_scheme_protocol`
+- [`10e362d0`](https://www.github.com/tauri-apps/tauri/commit/10e362d098c9bed48f832bad471fb2fab83ab0bb)([#7432](https://www.github.com/tauri-apps/tauri/pull/7432)) Added `UpdateBuilder::endpoints` to add request endpoints at runtime.
+- [`10e362d0`](https://www.github.com/tauri-apps/tauri/commit/10e362d098c9bed48f832bad471fb2fab83ab0bb)([#7432](https://www.github.com/tauri-apps/tauri/pull/7432)) Added `UpdateResponse::header` and `UpdateResponse::remove_header` to modify the update download request headers.
+
+### Enhancements
+
+- [`757e959e`](https://www.github.com/tauri-apps/tauri/commit/757e959eb276ed535cfddb0dea8897c56441c644)([#7344](https://www.github.com/tauri-apps/tauri/pull/7344)) Open links externally when `<base target="_blank" />` exists
+- [`c9827338`](https://www.github.com/tauri-apps/tauri/commit/c98273387c0ffbb8d0de78ce17006411a1f503ee)([#7416](https://www.github.com/tauri-apps/tauri/pull/7416)) Enhance `readDir` API error with path information.
+- [`58d6b899`](https://www.github.com/tauri-apps/tauri/commit/58d6b899e21d37bb42810890d289deb57f2273bd)([#7636](https://www.github.com/tauri-apps/tauri/pull/7636)) Add `append` option to `FsOptions` in the `fs` JS module, used in `writeTextFile` and `writeBinaryFile`, to be able to append to existing files instead of overwriting it.
+- [`9aa34ada`](https://www.github.com/tauri-apps/tauri/commit/9aa34ada5769dbefa7dfe5f7a6288b3d20b294e4)([#7645](https://www.github.com/tauri-apps/tauri/pull/7645)) Add setting to switch to `http://<scheme>.localhost/` for custom protocols on Windows.
+
+### Bug Fixes
+
+- [`4bf1e85e`](https://www.github.com/tauri-apps/tauri/commit/4bf1e85e6bf85a7ec92d50c8465bc0588a6399d8)([#7722](https://www.github.com/tauri-apps/tauri/pull/7722)) Properly respect the `focused` option when creating the webview.
+- [`0797a002`](https://www.github.com/tauri-apps/tauri/commit/0797a002caad29cd8bedccf01f64bf3b45a5e528)([#7746](https://www.github.com/tauri-apps/tauri/pull/7746)) On macOS, fixed tapping on custom title bar doesn't maximize the window.
+- [`1a3dcdb8`](https://www.github.com/tauri-apps/tauri/commit/1a3dcdb8302fad511f2c1cd418fbc4cff0bd62ac)([#7185](https://www.github.com/tauri-apps/tauri/pull/7185)) On Windows, fix NSIS installers requiring administrator rights failing to be launched by updater.
+- [`fa7f9b77`](https://www.github.com/tauri-apps/tauri/commit/fa7f9b77ab8f0c890e9d7b120901610e0d3e4c46)([#7341](https://www.github.com/tauri-apps/tauri/pull/7341)) Fix updater not following endpoint redirects.
+- [`6fbd6dba`](https://www.github.com/tauri-apps/tauri/commit/6fbd6dba5290dc017ab0ba5a44cf4358b022836f)([#17](https://www.github.com/tauri-apps/tauri/pull/17)) Fix the validation of `std::env::current_exe` warn the user if AppImage is not mounted instead of panicking
+
+### Dependencies
+
+- Upgraded to `tauri-utils@1.5.0`
+- Upgraded to `tauri-runtime-wry@0.14.1`
+- Upgraded to `tauri-runtime@0.14.1`
+- Upgraded to `tauri-macros@1.4.1`
+
 ## \[1.4.1]
 
 ### Bug Fixes

+ 2 - 1
core/tauri/Cargo.toml

@@ -71,7 +71,6 @@ infer = { version = "0.15", optional = true }
 png = { version = "0.17", optional = true }
 ico = { version = "0.3.0", optional = true }
 http-range = { version = "0.1.4", optional = true }
-window-vibrancy = "0.4"
 
 [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"windows\", target_os = \"macos\"))".dependencies]
 muda = { version = "0.8", default-features = false }
@@ -86,9 +85,11 @@ webkit2gtk = { version = "1.1", features = [ "v2_38" ] }
 embed_plist = "1.2"
 cocoa = "0.25"
 objc = "0.2"
+window-vibrancy = "0.4"
 
 [target."cfg(windows)".dependencies]
 webview2-com = "0.25"
+window-vibrancy = "0.4"
 
   [target."cfg(windows)".dependencies.windows]
   version = "0.48"

+ 6 - 0
core/tauri/src/app.rs

@@ -1393,6 +1393,11 @@ impl<R: Runtime> Builder<R> {
   /// Similar to [`Self::register_uri_scheme_protocol`] but with an asynchronous responder that allows you
   /// to process the request in a separate thread and respond asynchronously.
   ///
+  /// # Arguments
+  ///
+  /// * `uri_scheme` The URI scheme to register, such as `example`.
+  /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`.
+  ///
   /// # Examples
   /// ```
   /// tauri::Builder::default()
@@ -1485,6 +1490,7 @@ impl<R: Runtime> Builder<R> {
     for config in manager.config().tauri.windows.clone() {
       let label = config.label.clone();
       let webview_attributes = WebviewAttributes::from(&config);
+
       self.pending_windows.push(PendingWindow::with_config(
         config,
         webview_attributes,

+ 21 - 1
core/tauri/src/lib.rs

@@ -548,7 +548,7 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
     self.manager().package_info()
   }
 
-  /// Emits a event to all windows.
+  /// Emits an event to all windows.
   ///
   /// Only the webviews receives this event.
   /// To trigger Rust listeners, use [`Self::trigger_global`], [`Window::trigger`] or [`Window::emit_and_trigger`].
@@ -567,6 +567,26 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
     self.manager().emit_filter(event, None, payload, |_| true)
   }
 
+  /// Emits an event to windows matching the filter critera.
+  ///
+  /// # Examples
+  /// ```
+  /// use tauri::Manager;
+  ///
+  /// #[tauri::command]
+  /// fn synchronize(app: tauri::AppHandle) {
+  ///   // emits the synchronized event to all windows
+  ///   app.emit_filter("synchronized", (), |w| w.label().starts_with("foo-"));
+  /// }
+  /// ```
+  fn emit_filter<S, F>(&self, event: &str, payload: S, filter: F) -> Result<()>
+  where
+    S: Serialize + Clone,
+    F: Fn(&Window<R>) -> bool,
+  {
+    self.manager().emit_filter(event, None, payload, filter)
+  }
+
   /// Emits an event to the window with the specified label.
   ///
   /// # Examples

+ 33 - 9
core/tauri/src/manager.rs

@@ -27,6 +27,7 @@ use tauri_utils::{
   html::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN},
 };
 
+use crate::window::WindowEmitArgs;
 use crate::{
   app::{
     AppHandle, GlobalWindowEvent, GlobalWindowEventListener, OnPageLoad, PageLoadPayload,
@@ -242,7 +243,7 @@ pub struct InnerWindowManager<R: Runtime> {
 
   package_info: PackageInfo,
   /// The webview protocols available to all windows.
-  uri_scheme_protocols: HashMap<String, Arc<UriSchemeProtocol<R>>>,
+  uri_scheme_protocols: Mutex<HashMap<String, Arc<UriSchemeProtocol<R>>>>,
   /// A set containing a reference to the active menus, including
   /// the app-wide menu and the window-specific menus
   ///
@@ -370,7 +371,7 @@ impl<R: Runtime> WindowManager<R> {
         tray_icon: context.tray_icon,
         package_info: context.package_info,
         pattern: context.pattern,
-        uri_scheme_protocols,
+        uri_scheme_protocols: Mutex::new(uri_scheme_protocols),
         #[cfg(desktop)]
         menus: Default::default(),
         #[cfg(desktop)]
@@ -488,6 +489,20 @@ impl<R: Runtime> WindowManager<R> {
     self.inner.invoke_responder.clone()
   }
 
+  pub(crate) fn register_uri_scheme_protocol<N: Into<String>>(
+    &self,
+    uri_scheme: N,
+    protocol: Arc<UriSchemeProtocol<R>>,
+  ) {
+    let uri_scheme = uri_scheme.into();
+    self
+      .inner
+      .uri_scheme_protocols
+      .lock()
+      .unwrap()
+      .insert(uri_scheme, protocol);
+  }
+
   /// Get the base path to serve data from.
   ///
   /// * In dev mode, this will be based on the `devPath` configuration value.
@@ -555,6 +570,8 @@ impl<R: Runtime> WindowManager<R> {
     }
     .render_default(&Default::default())?;
 
+    let mut webview_attributes = pending.webview_attributes;
+
     let ipc_init = IpcJavascript {
       isolation_origin: &match self.pattern() {
         #[cfg(feature = "isolation")]
@@ -564,8 +581,6 @@ impl<R: Runtime> WindowManager<R> {
     }
     .render_default(&Default::default())?;
 
-    let mut webview_attributes = pending.webview_attributes;
-
     let mut window_labels = window_labels.to_vec();
     let l = label.to_string();
     if !window_labels.contains(&l) {
@@ -619,7 +634,7 @@ impl<R: Runtime> WindowManager<R> {
 
     let mut registered_scheme_protocols = Vec::new();
 
-    for (uri_scheme, protocol) in &self.inner.uri_scheme_protocols {
+    for (uri_scheme, protocol) in &*self.inner.uri_scheme_protocols.lock().unwrap() {
       registered_scheme_protocols.push(uri_scheme.clone());
       let protocol = protocol.clone();
       let app_handle = Mutex::new(app_handle.clone());
@@ -957,8 +972,8 @@ impl<R: Runtime> WindowManager<R> {
           let html = String::from_utf8_lossy(&body).into_owned();
           // naive way to check if it's an html
           if html.contains('<') && html.contains('>') {
-            let mut document = tauri_utils::html::parse(html);
-            tauri_utils::html::inject_csp(&mut document, &csp.to_string());
+            let document = tauri_utils::html::parse(html);
+            tauri_utils::html::inject_csp(&document, &csp.to_string());
             url.set_path(&format!("{},{}", mime::TEXT_HTML, document.to_string()));
           }
         }
@@ -1142,12 +1157,13 @@ impl<R: Runtime> WindowManager<R> {
     S: Serialize + Clone,
     F: Fn(&Window<R>) -> bool,
   {
+    let emit_args = WindowEmitArgs::from(event, source_window_label, payload)?;
     assert_event_name_is_valid(event);
     self
-      .windows_lock()
+      .windows()
       .values()
       .filter(|&w| filter(w))
-      .try_for_each(|window| window.emit_internal(event, source_window_label, payload.clone()))
+      .try_for_each(|window| window.emit_internal(&emit_args))
   }
 
   pub fn eval_script_all<S: Into<String>>(&self, script: S) -> crate::Result<()> {
@@ -1195,6 +1211,14 @@ impl<R: Runtime> WindowManager<R> {
     self.listeners().once(event, window, handler)
   }
 
+  pub fn event_listeners_object_name(&self) -> &str {
+    self.inner.listeners.listeners_object_name()
+  }
+
+  pub fn event_emit_function_name(&self) -> &str {
+    self.inner.listeners.function_name()
+  }
+
   pub fn unlisten(&self, id: EventId) {
     self.listeners().unlisten(id)
   }

+ 118 - 2
core/tauri/src/plugin.rs

@@ -5,9 +5,10 @@
 //! The Tauri plugin extension to expand Tauri functionality.
 
 use crate::{
-  app::PageLoadPayload,
+  app::{PageLoadPayload, UriSchemeResponder},
   error::Error,
   ipc::{Invoke, InvokeHandler},
+  manager::UriSchemeProtocol,
   utils::config::PluginConfig,
   AppHandle, RunEvent, Runtime, Window,
 };
@@ -16,7 +17,7 @@ use serde_json::Value as JsonValue;
 use tauri_macros::default_runtime;
 use url::Url;
 
-use std::{fmt, sync::Arc};
+use std::{borrow::Cow, collections::HashMap, fmt, sync::Arc};
 
 /// Mobile APIs.
 #[cfg(mobile)]
@@ -210,6 +211,7 @@ pub struct Builder<R: Runtime, C: DeserializeOwned = ()> {
   on_webview_ready: Box<OnWebviewReady<R>>,
   on_event: Box<OnEvent<R>>,
   on_drop: Option<Box<OnDrop<R>>>,
+  uri_scheme_protocols: HashMap<String, Arc<UriSchemeProtocol<R>>>,
 }
 
 impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
@@ -225,6 +227,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
       on_webview_ready: Box::new(|_| ()),
       on_event: Box::new(|_, _| ()),
       on_drop: None,
+      uri_scheme_protocols: Default::default(),
     }
   }
 
@@ -460,6 +463,111 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
     self
   }
 
+  /// Registers a URI scheme protocol available to all webviews.
+  /// Leverages [setURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/2875766-seturlschemehandler) on macOS,
+  /// [AddWebResourceRequestedFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.addwebresourcerequestedfilter?view=webview2-dotnet-1.0.774.44) on Windows
+  /// and [webkit-web-context-register-uri-scheme](https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#webkit-web-context-register-uri-scheme) on Linux.
+  ///
+  /// # Known limitations
+  ///
+  /// URI scheme protocols are registered when the webview is created. Due to this limitation, if the plugin is registed after a webview has been created, this protocol won't be available.
+  ///
+  /// # Arguments
+  ///
+  /// * `uri_scheme` The URI scheme to register, such as `example`.
+  /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`.
+  ///
+  /// # Examples
+  ///
+  /// ```rust
+  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
+  ///
+  /// fn init<R: Runtime>() -> TauriPlugin<R> {
+  ///   Builder::new("myplugin")
+  ///     .register_uri_scheme_protocol("myscheme", |app, req| {
+  ///       http::Response::builder().body(Vec::new()).unwrap()
+  ///     })
+  ///     .build()
+  /// }
+  /// ```
+  #[must_use]
+  pub fn register_uri_scheme_protocol<
+    N: Into<String>,
+    T: Into<Cow<'static, [u8]>>,
+    H: Fn(&AppHandle<R>, http::Request<Vec<u8>>) -> http::Response<T> + Send + Sync + 'static,
+  >(
+    mut self,
+    uri_scheme: N,
+    protocol: H,
+  ) -> Self {
+    self.uri_scheme_protocols.insert(
+      uri_scheme.into(),
+      Arc::new(UriSchemeProtocol {
+        protocol: Box::new(move |app, request, responder| {
+          responder.respond(protocol(app, request))
+        }),
+      }),
+    );
+    self
+  }
+
+  /// Similar to [`Self::register_uri_scheme_protocol`] but with an asynchronous responder that allows you
+  /// to process the request in a separate thread and respond asynchronously.
+  ///
+  /// # Arguments
+  ///
+  /// * `uri_scheme` The URI scheme to register, such as `example`.
+  /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`.
+  ///
+  /// # Examples
+  ///
+  /// ```rust
+  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
+  ///
+  /// fn init<R: Runtime>() -> TauriPlugin<R> {
+  ///   Builder::new("myplugin")
+  ///     .register_asynchronous_uri_scheme_protocol("app-files", |_app, request, responder| {
+  ///       // skip leading `/`
+  ///       let path = request.uri().path()[1..].to_string();
+  ///       std::thread::spawn(move || {
+  ///         if let Ok(data) = std::fs::read(path) {
+  ///           responder.respond(
+  ///             http::Response::builder()
+  ///               .body(data)
+  ///               .unwrap()
+  ///           );
+  ///         } else {
+  ///           responder.respond(
+  ///             http::Response::builder()
+  ///               .status(http::StatusCode::BAD_REQUEST)
+  ///               .header(http::header::CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())
+  ///               .body("failed to read file".as_bytes().to_vec())
+  ///               .unwrap()
+  ///           );
+  ///         }
+  ///       });
+  ///     })
+  ///     .build()
+  /// }
+  /// ```
+  #[must_use]
+  pub fn register_asynchronous_uri_scheme_protocol<
+    N: Into<String>,
+    H: Fn(&AppHandle<R>, http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync + 'static,
+  >(
+    mut self,
+    uri_scheme: N,
+    protocol: H,
+  ) -> Self {
+    self.uri_scheme_protocols.insert(
+      uri_scheme.into(),
+      Arc::new(UriSchemeProtocol {
+        protocol: Box::new(protocol),
+      }),
+    );
+    self
+  }
+
   /// Builds the [TauriPlugin].
   pub fn build(self) -> TauriPlugin<R, C> {
     TauriPlugin {
@@ -473,6 +581,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
       on_webview_ready: self.on_webview_ready,
       on_event: self.on_event,
       on_drop: self.on_drop,
+      uri_scheme_protocols: self.uri_scheme_protocols,
     }
   }
 }
@@ -489,6 +598,7 @@ pub struct TauriPlugin<R: Runtime, C: DeserializeOwned = ()> {
   on_webview_ready: Box<OnWebviewReady<R>>,
   on_event: Box<OnEvent<R>>,
   on_drop: Option<Box<OnDrop<R>>>,
+  uri_scheme_protocols: HashMap<String, Arc<UriSchemeProtocol<R>>>,
 }
 
 impl<R: Runtime, C: DeserializeOwned> Drop for TauriPlugin<R, C> {
@@ -521,6 +631,12 @@ impl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> {
         },
       )?;
     }
+
+    for (uri_scheme, protocol) in &self.uri_scheme_protocols {
+      app
+        .manager
+        .register_uri_scheme_protocol(uri_scheme, protocol.clone())
+    }
     Ok(())
   }
 

+ 25 - 10
core/tauri/src/window/mod.rs

@@ -71,6 +71,26 @@ struct WindowCreatedEvent {
   label: String,
 }
 
+pub(crate) struct WindowEmitArgs {
+  pub event: String,
+  pub source_window_label: String,
+  pub payload: String,
+}
+
+impl WindowEmitArgs {
+  pub fn from<S: Serialize>(
+    event: &str,
+    source_window_label: Option<&str>,
+    payload: S,
+  ) -> crate::Result<Self> {
+    Ok(WindowEmitArgs {
+      event: serde_json::to_string(event)?,
+      source_window_label: serde_json::to_string(&source_window_label)?,
+      payload: serde_json::to_string(&payload)?,
+    })
+  }
+}
+
 /// Monitor descriptor.
 #[derive(Debug, Clone, Serialize)]
 #[serde(rename_all = "camelCase")]
@@ -2453,18 +2473,13 @@ impl<R: Runtime> Window<R> {
     self.emit(event, payload)
   }
 
-  pub(crate) fn emit_internal<S: Serialize>(
-    &self,
-    event: &str,
-    source_window_label: Option<&str>,
-    payload: S,
-  ) -> crate::Result<()> {
+  pub(crate) fn emit_internal(&self, emit_args: &WindowEmitArgs) -> crate::Result<()> {
     self.eval(&format!(
       "(function () {{ const fn = window['{}']; fn && fn({{event: {}, windowLabel: {}, payload: {}}}) }})()",
-      self.manager.listeners().function_name(),
-      serde_json::to_string(event)?,
-      serde_json::to_string(&source_window_label)?,
-      serde_json::to_value(payload)?,
+      self.manager.event_emit_function_name(),
+      emit_args.event,
+      emit_args.source_window_label,
+      emit_args.payload
     ))?;
     Ok(())
   }

+ 1 - 0
core/tests/app-updater/frameworks/test.framework/Headers

@@ -0,0 +1 @@
+Versions/Current/Headers

+ 1 - 0
core/tests/app-updater/frameworks/test.framework/Modules

@@ -0,0 +1 @@
+Versions/Current/Modules

+ 1 - 0
core/tests/app-updater/frameworks/test.framework/Resources

@@ -0,0 +1 @@
+Versions/Current/Resources

+ 18 - 0
core/tests/app-updater/frameworks/test.framework/Versions/A/Headers/test.h

@@ -0,0 +1,18 @@
+//
+//  test.h
+//  test
+//
+//  Created by Trey Smith on 9/15/23.
+//
+
+#import <Foundation/Foundation.h>
+
+//! Project version number for test.
+FOUNDATION_EXPORT double testVersionNumber;
+
+//! Project version string for test.
+FOUNDATION_EXPORT const unsigned char testVersionString[];
+
+// In this header, you should import all the public headers of your framework using statements like #import <test/PublicHeader.h>
+
+

+ 6 - 0
core/tests/app-updater/frameworks/test.framework/Versions/A/Modules/module.modulemap

@@ -0,0 +1,6 @@
+framework module test {
+  umbrella header "test.h"
+
+  export *
+  module * { export * }
+}

+ 46 - 0
core/tests/app-updater/frameworks/test.framework/Versions/A/Resources/Info.plist

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>BuildMachineOSBuild</key>
+	<string>22D68</string>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>test</string>
+	<key>CFBundleIdentifier</key>
+	<string>com.tauri.test</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>test</string>
+	<key>CFBundlePackageType</key>
+	<string>FMWK</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleSupportedPlatforms</key>
+	<array>
+		<string>MacOSX</string>
+	</array>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>DTCompiler</key>
+	<string>com.apple.compilers.llvm.clang.1_0</string>
+	<key>DTPlatformBuild</key>
+	<string></string>
+	<key>DTPlatformName</key>
+	<string>macosx</string>
+	<key>DTPlatformVersion</key>
+	<string>13.3</string>
+	<key>DTSDKBuild</key>
+	<string>22E245</string>
+	<key>DTSDKName</key>
+	<string>macosx13.3</string>
+	<key>DTXcode</key>
+	<string>1431</string>
+	<key>DTXcodeBuild</key>
+	<string>14E300c</string>
+	<key>LSMinimumSystemVersion</key>
+	<string>13.2</string>
+</dict>
+</plist>

+ 142 - 0
core/tests/app-updater/frameworks/test.framework/Versions/A/_CodeSignature/CodeResources

@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>files</key>
+	<dict>
+		<key>Resources/Info.plist</key>
+		<data>
+		/aPV7Q20g0elr7OiZJoUNggTOcg=
+		</data>
+	</dict>
+	<key>files2</key>
+	<dict>
+		<key>Headers/test.h</key>
+		<dict>
+			<key>hash2</key>
+			<data>
+			5RA6Mnq5sNoaC4wKcFe6zymVmEL5Vb44G4BGqFjgZMM=
+			</data>
+		</dict>
+		<key>Modules/module.modulemap</key>
+		<dict>
+			<key>hash2</key>
+			<data>
+			C6uLLSnQu9M2qLElVCkeo2JpnvWMxtArinQzmlh3v2A=
+			</data>
+		</dict>
+		<key>Resources/Info.plist</key>
+		<dict>
+			<key>hash2</key>
+			<data>
+			nPMotNIMgvMfHtkRdpeehzfBiCZLnksfiD3nldUPzTE=
+			</data>
+		</dict>
+	</dict>
+	<key>rules</key>
+	<dict>
+		<key>^Resources/</key>
+		<true/>
+		<key>^Resources/.*\.lproj/</key>
+		<dict>
+			<key>optional</key>
+			<true/>
+			<key>weight</key>
+			<real>1000</real>
+		</dict>
+		<key>^Resources/.*\.lproj/locversion.plist$</key>
+		<dict>
+			<key>omit</key>
+			<true/>
+			<key>weight</key>
+			<real>1100</real>
+		</dict>
+		<key>^Resources/Base\.lproj/</key>
+		<dict>
+			<key>weight</key>
+			<real>1010</real>
+		</dict>
+		<key>^version.plist$</key>
+		<true/>
+	</dict>
+	<key>rules2</key>
+	<dict>
+		<key>.*\.dSYM($|/)</key>
+		<dict>
+			<key>weight</key>
+			<real>11</real>
+		</dict>
+		<key>^(.*/)?\.DS_Store$</key>
+		<dict>
+			<key>omit</key>
+			<true/>
+			<key>weight</key>
+			<real>2000</real>
+		</dict>
+		<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
+		<dict>
+			<key>nested</key>
+			<true/>
+			<key>weight</key>
+			<real>10</real>
+		</dict>
+		<key>^.*</key>
+		<true/>
+		<key>^Info\.plist$</key>
+		<dict>
+			<key>omit</key>
+			<true/>
+			<key>weight</key>
+			<real>20</real>
+		</dict>
+		<key>^PkgInfo$</key>
+		<dict>
+			<key>omit</key>
+			<true/>
+			<key>weight</key>
+			<real>20</real>
+		</dict>
+		<key>^Resources/</key>
+		<dict>
+			<key>weight</key>
+			<real>20</real>
+		</dict>
+		<key>^Resources/.*\.lproj/</key>
+		<dict>
+			<key>optional</key>
+			<true/>
+			<key>weight</key>
+			<real>1000</real>
+		</dict>
+		<key>^Resources/.*\.lproj/locversion.plist$</key>
+		<dict>
+			<key>omit</key>
+			<true/>
+			<key>weight</key>
+			<real>1100</real>
+		</dict>
+		<key>^Resources/Base\.lproj/</key>
+		<dict>
+			<key>weight</key>
+			<real>1010</real>
+		</dict>
+		<key>^[^/]+$</key>
+		<dict>
+			<key>nested</key>
+			<true/>
+			<key>weight</key>
+			<real>10</real>
+		</dict>
+		<key>^embedded\.provisionprofile$</key>
+		<dict>
+			<key>weight</key>
+			<real>20</real>
+		</dict>
+		<key>^version\.plist$</key>
+		<dict>
+			<key>weight</key>
+			<real>20</real>
+		</dict>
+	</dict>
+</dict>
+</plist>

BIN
core/tests/app-updater/frameworks/test.framework/Versions/A/test


+ 1 - 0
core/tests/app-updater/frameworks/test.framework/Versions/Current

@@ -0,0 +1 @@
+A

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
examples/api/dist/assets/index.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 209 - 187
examples/api/src-tauri/Cargo.lock


+ 14 - 14
examples/multiwindow/index.html

@@ -14,17 +14,17 @@
     <div id="response"></div>
 
     <script>
-      var WebviewWindow = window.__TAURI__.window.WebviewWindow
-      var appWindow = window.__TAURI__.window.appWindow
-      var windowLabel = appWindow.label
-      var windowLabelContainer = document.getElementById('window-label')
+      const WebviewWindow = window.__TAURI__.window.WebviewWindow
+      const appWindow = window.__TAURI__.window.appWindow
+      const windowLabel = appWindow.label
+      const windowLabelContainer = document.getElementById('window-label')
       windowLabelContainer.innerText = 'This is the ' + windowLabel + ' window.'
 
-      var container = document.getElementById('container')
+      const container = document.getElementById('container')
 
       function createWindowMessageBtn(label) {
-        var tauriWindow = WebviewWindow.getByLabel(label)
-        var button = document.createElement('button')
+        const tauriWindow = WebviewWindow.getByLabel(label)
+        const button = document.createElement('button')
         button.innerText = 'Send message to ' + label
         button.addEventListener('click', function () {
           tauriWindow.emit('clicked', 'message from ' + windowLabel)
@@ -33,6 +33,7 @@
       }
 
       // global listener
+      const responseContainer = document.getElementById('response')
       window.__TAURI__.event.listen('clicked', function (event) {
         responseContainer.innerHTML +=
           'Got ' + JSON.stringify(event) + ' on global listener\n\n'
@@ -41,17 +42,16 @@
         createWindowMessageBtn(event.payload.label)
       })
 
-      var responseContainer = document.getElementById('response')
       // listener tied to this window
       appWindow.listen('clicked', function (event) {
         responseContainer.innerText +=
           'Got ' + JSON.stringify(event) + ' on window listener\n\n'
       })
 
-      var createWindowButton = document.createElement('button')
+      const createWindowButton = document.createElement('button')
       createWindowButton.innerHTML = 'Create window'
       createWindowButton.addEventListener('click', function () {
-        var webviewWindow = new WebviewWindow(
+        const webviewWindow = new WebviewWindow(
           Math.random().toString().replace('.', ''),
           {
             tabbingIdentifier: windowLabel
@@ -66,7 +66,7 @@
       })
       container.appendChild(createWindowButton)
 
-      var globalMessageButton = document.createElement('button')
+      const globalMessageButton = document.createElement('button')
       globalMessageButton.innerHTML = 'Send global message'
       globalMessageButton.addEventListener('click', function () {
         // emit to all windows
@@ -74,9 +74,9 @@
       })
       container.appendChild(globalMessageButton)
 
-      var allWindows = window.__TAURI__.window.getAll()
-      for (var index in allWindows) {
-        var label = allWindows[index].label
+      const allWindows = window.__TAURI__.window.getAll()
+      for (const index in allWindows) {
+        const label = allWindows[index].label
         if (label === windowLabel) {
           continue
         }

+ 9 - 9
examples/parent-window/index.html

@@ -14,15 +14,15 @@
     <div id="response"></div>
 
     <script>
-      var WebviewWindow = window.__TAURI__.window.WebviewWindow
-      var thisTauriWindow = window.__TAURI__.window.getCurrent()
-      var windowLabel = thisTauriWindow.label
-      var windowLabelContainer = document.getElementById('window-label')
+      const WebviewWindow = window.__TAURI__.window.WebviewWindow
+      const thisTauriWindow = window.__TAURI__.window.getCurrent()
+      const windowLabel = thisTauriWindow.label
+      const windowLabelContainer = document.getElementById('window-label')
       windowLabelContainer.innerText = 'This is the ' + windowLabel + ' window.'
 
-      var container = document.getElementById('container')
+      const container = document.getElementById('container')
 
-      var responseContainer = document.getElementById('response')
+      const responseContainer = document.getElementById('response')
       function runCommand(commandName, args, optional) {
         window.__TAURI__
           .invoke(commandName, args)
@@ -37,9 +37,9 @@
         responseContainer.innerText += 'Got window-created event\n\n'
       })
 
-      var createWindowButton = document.createElement('button')
-      var windowId = Math.random().toString().replace('.', '')
-      var windowNumber = 1
+      const createWindowButton = document.createElement('button')
+      const windowId = Math.random().toString().replace('.', '')
+      const windowNumber = 1
       createWindowButton.innerHTML = 'Create child window ' + windowNumber
       createWindowButton.addEventListener('click', function () {
         runCommand('create_child_window', {

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 292 - 141
examples/resources/src-tauri/Cargo.lock


+ 1 - 1
examples/resources/src-tauri/Cargo.toml

@@ -6,7 +6,7 @@ edition = "2021"
 rust-version = "1.70"
 
 [build-dependencies]
-tauri-build = { path = "../../../core/tauri-build", features = [ "codegen" ] }
+tauri-build = { path = "../../../core/tauri-build", features = ["codegen"] }
 
 [dependencies]
 serde_json = "1.0"

+ 0 - 1
tooling/api/.gitignore

@@ -65,4 +65,3 @@ package-lock.json
 
 # Documentation output
 docs/*
-!docs/js-api.json

+ 20 - 0
tooling/api/CHANGELOG.md

@@ -87,6 +87,26 @@
 - First mobile alpha release!
   - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08
 
+## \[1.5.1]
+
+### New Features
+
+- [`2b0212af`](https://www.github.com/tauri-apps/tauri/commit/2b0212af49c386e52bb2357381813d6d435ec4af)([#7961](https://www.github.com/tauri-apps/tauri/pull/7961)) Add `mockConvertFileSrc` in `mocks` module, to mock `convertFileSrc` function.
+
+## \[1.5.0]
+
+### New Features
+
+- [`6c408b73`](https://www.github.com/tauri-apps/tauri/commit/6c408b736c7aa2a0a91f0a40d45a2b7a7dedfe78)([#7269](https://www.github.com/tauri-apps/tauri/pull/7269)) Add option to specify notification sound.
+
+### Enhancements
+
+- [`58d6b899`](https://www.github.com/tauri-apps/tauri/commit/58d6b899e21d37bb42810890d289deb57f2273bd)([#7636](https://www.github.com/tauri-apps/tauri/pull/7636)) Add `append` option to `FsOptions` in the `fs` JS module, used in `writeTextFile` and `writeBinaryFile`, to be able to append to existing files instead of overwriting it.
+
+### Bug Fixes
+
+- [`2eab1505`](https://www.github.com/tauri-apps/tauri/commit/2eab1505632ff71431d4c31c49b5afc78fa5b9dd)([#7394](https://www.github.com/tauri-apps/tauri/pull/7394)) Fix `Body.form` static not reading and sending entries of type `Blob` (including subclasses such as `File`)
+
 ## \[1.4.0]
 
 ### New Features

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
tooling/api/docs/js-api.json


+ 4 - 4
tooling/api/package.json

@@ -11,14 +11,13 @@
     "./package.json": "./package.json"
   },
   "scripts": {
-    "build": "yarn tsup && node ./scripts/after-build.cjs && yarn generate-docs",
+    "build": "yarn tsup && node ./scripts/after-build.cjs",
     "npm-pack": "yarn build && cd ./dist && npm pack",
     "npm-publish": "yarn build && cd ./dist && yarn publish --access public --loglevel silly --tag next",
     "lint": "eslint --ext ts \"./src/**/*.ts\"",
     "lint-fix": "eslint --fix --ext ts \"./src/**/*.ts\"",
     "format": "prettier --write --end-of-line=auto \"./**/*.{cjs,js,jsx,ts,tsx,html,css,json}\" --ignore-path ../../.prettierignore",
-    "format:check": "prettier --check --end-of-line=auto \"./**/*.{cjs,js,jsx,ts,tsx,html,css,json}\" --ignore-path ../../.prettierignore",
-    "generate-docs": "typedoc --plugin typedoc-plugin-markdown --plugin typedoc-plugin-mdn-links"
+    "format:check": "prettier --check --end-of-line=auto \"./**/*.{cjs,js,jsx,ts,tsx,html,css,json}\" --ignore-path ../../.prettierignore"
   },
   "repository": {
     "type": "git",
@@ -59,6 +58,7 @@
     "typescript": "5.2.2"
   },
   "resolutions": {
-    "semver": ">=7.5.2"
+    "semver": ">=7.5.2",
+    "optionator": ">=0.9.3"
   }
 }

+ 32 - 0
tooling/api/src/mocks.ts

@@ -134,6 +134,36 @@ export function mockWindows(
   }
 }
 
+/**
+ * Mock `convertFileSrc` function
+ *
+ *
+ * @example
+ * ```js
+ * import { mockConvertFileSrc } from "@tauri-apps/api/mocks";
+ * import { convertFileSrc } from "@tauri-apps/api/tauri";
+ *
+ * mockConvertFileSrc("windows")
+ *
+ * const url = convertFileSrc("C:\\Users\\user\\file.txt")
+ * ```
+ *
+ * @param osName The operating system to mock, can be one of linux, macos, or windows
+ *
+ * @since 1.6.0
+ */
+export function mockConvertFileSrc(
+  osName: string
+): void {
+  window.__TAURI_INTERNALS__ = window.__TAURI_INTERNALS__ ?? {}
+  window.__TAURI_INTERNALS__.convertFileSrc = function (filePath, protocol = 'asset') {
+    const path = encodeURIComponent(filePath)
+    return osName === 'windows'
+      ? `http://${protocol}.localhost/${path}`
+      : `${protocol}://localhost/${path}`
+  }
+}
+
 /**
  * Clears mocked functions/data injected by the other functions in this module.
  * When using a test runner that doesn't provide a fresh window object for each test, calling this function will reset tauri specific properties.
@@ -161,6 +191,8 @@ export function mockWindows(
  * @since 1.0.0
  */
 export function clearMocks(): void {
+  // @ts-expect-error "The operand of a 'delete' operator must be optional' does not matter in this case
+  delete window.__TAURI_INTERNALS__.convertFileSrc
   // @ts-expect-error "The operand of a 'delete' operator must be optional' does not matter in this case
   delete window.__TAURI_INTERNALS__.ipc
   // @ts-expect-error "The operand of a 'delete' operator must be optional' does not matter in this case

+ 0 - 18
tooling/api/typedoc.json

@@ -1,18 +0,0 @@
-{
-  "entryPoints": [
-    "src/dpi.ts",
-    "src/event.ts",
-    "src/mocks.ts",
-    "src/path.ts",
-    "src/primitives.ts",
-    "src/window.ts",
-    "src/app.ts"
-  ],
-  "githubPages": false,
-  "readme": "none",
-  "hideInPageTOC": true,
-  "hideMembersSymbol": true,
-  "out": "docs",
-  "json": "docs/js-api.json",
-  "pretty": false
-}

+ 12 - 12
tooling/api/yarn.lock

@@ -322,9 +322,9 @@ ansi-regex@^5.0.1:
   integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
 
 ansi-sequence-parser@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.0.tgz#4d790f31236ac20366b23b3916b789e1bde39aed"
-  integrity sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz#e0aa1cdcbc8f8bb0b5bca625aac41f5f056973cf"
+  integrity sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==
 
 ansi-styles@^4.1.0:
   version "4.3.0"
@@ -1236,12 +1236,12 @@ graphemer@^1.4.0:
   integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
 
 handlebars@^4.7.7:
-  version "4.7.7"
-  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
-  integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
+  version "4.7.8"
+  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9"
+  integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==
   dependencies:
     minimist "^1.2.5"
-    neo-async "^2.6.0"
+    neo-async "^2.6.2"
     source-map "^0.6.1"
     wordwrap "^1.0.0"
   optionalDependencies:
@@ -1701,7 +1701,7 @@ natural-compare@^1.4.0:
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
   integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
 
-neo-async@^2.6.0:
+neo-async@^2.6.2:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
   integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
@@ -1805,7 +1805,7 @@ onetime@^5.1.2:
   dependencies:
     mimic-fn "^2.1.0"
 
-optionator@^0.9.3:
+optionator@>=0.9.3, optionator@^0.9.3:
   version "0.9.3"
   resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64"
   integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==
@@ -2050,9 +2050,9 @@ shebang-regex@^3.0.0:
   integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
 
 shiki@^0.14.1:
-  version "0.14.2"
-  resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.14.2.tgz#d51440800b701392b31ce2336036058e338247a1"
-  integrity sha512-ltSZlSLOuSY0M0Y75KA+ieRaZ0Trf5Wl3gutE7jzLuIcWxLp5i/uEnLoQWNvgKXQ5OMpGkJnVMRLAuzjc0LJ2A==
+  version "0.14.5"
+  resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.14.5.tgz#375dd214e57eccb04f0daf35a32aa615861deb93"
+  integrity sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw==
   dependencies:
     ansi-sequence-parser "^1.1.0"
     jsonc-parser "^3.2.0"

+ 157 - 52
tooling/bench/Cargo.lock

@@ -4,9 +4,15 @@ version = 3
 
 [[package]]
 name = "anyhow"
-version = "1.0.66"
+version = "1.0.71"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
+checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
 name = "bitflags"
@@ -14,21 +20,54 @@ version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
+[[package]]
+name = "errno"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
 [[package]]
 name = "fastrand"
-version = "1.8.0"
+version = "1.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
 dependencies = [
  "instant",
 ]
 
+[[package]]
+name = "hermit-abi"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
+
 [[package]]
 name = "instant"
 version = "0.1.12"
@@ -38,74 +77,96 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "io-lifetimes"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys",
+]
+
 [[package]]
 name = "itoa"
-version = "1.0.4"
+version = "1.0.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
+checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a"
 
 [[package]]
 name = "libc"
-version = "0.2.137"
+version = "0.2.147"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
+checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.47"
+version = "1.0.64"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
+checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.21"
+version = "1.0.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
+checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
 dependencies = [
  "proc-macro2",
 ]
 
 [[package]]
 name = "redox_syscall"
-version = "0.2.16"
+version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
 dependencies = [
  "bitflags",
 ]
 
 [[package]]
-name = "remove_dir_all"
-version = "0.5.3"
+name = "rustix"
+version = "0.37.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
 dependencies = [
- "winapi",
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
 ]
 
 [[package]]
 name = "ryu"
-version = "1.0.11"
+version = "1.0.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
+checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9"
 
 [[package]]
 name = "serde"
-version = "1.0.147"
+version = "1.0.171"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
+checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.147"
+version = "1.0.171"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
+checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -114,9 +175,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.87"
+version = "1.0.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
+checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c"
 dependencies = [
  "itoa",
  "ryu",
@@ -125,9 +186,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.103"
+version = "2.0.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
+checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -147,23 +208,23 @@ dependencies = [
 
 [[package]]
 name = "tempfile"
-version = "3.3.0"
+version = "3.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6"
 dependencies = [
+ "autocfg",
  "cfg-if",
  "fastrand",
- "libc",
  "redox_syscall",
- "remove_dir_all",
- "winapi",
+ "rustix",
+ "windows-sys",
 ]
 
 [[package]]
 name = "time"
-version = "0.3.17"
+version = "0.3.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
+checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
 dependencies = [
  "itoa",
  "serde",
@@ -173,43 +234,87 @@ dependencies = [
 
 [[package]]
 name = "time-core"
-version = "0.1.0"
+version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
+checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
 
 [[package]]
 name = "time-macros"
-version = "0.2.6"
+version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
+checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
 dependencies = [
  "time-core",
 ]
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.5"
+version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
+checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73"
 
 [[package]]
-name = "winapi"
-version = "0.3.9"
+name = "windows-sys"
+version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
 dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
+ "windows-targets",
 ]
 
 [[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
+name = "windows-targets"
+version = "0.48.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
 
 [[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
+name = "windows_x86_64_msvc"
+version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"

+ 69 - 0
tooling/bundler/CHANGELOG.md

@@ -76,6 +76,75 @@
 - First mobile alpha release!
   - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08
 
+## \[1.4.4]
+
+### Enhancements
+
+- [`3880b42d`](https://www.github.com/tauri-apps/tauri/commit/3880b42d18f218b89998bb5ead1aacfbed9d6202)([#7974](https://www.github.com/tauri-apps/tauri/pull/7974)) Include notarytool log output on error message in case notarization fails.
+
+### Bug Fixes
+
+- [`be8e5aa3`](https://www.github.com/tauri-apps/tauri/commit/be8e5aa3071d9bc5d0bd24647e8168f312d11c8d)([#8042](https://www.github.com/tauri-apps/tauri/pull/8042)) Fixes duplicated newlines on command outputs.
+
+## \[1.4.3]
+
+### Bug Fixes
+
+- [`d0ae6750`](https://www.github.com/tauri-apps/tauri/commit/d0ae67503cdb2aeaadcea27af67285eea1cf3756)([#8012](https://www.github.com/tauri-apps/tauri/pull/8012)) Read `HTTP_PROXY` env var when downloading bundling resources on Windows.
+- [`113bcd7b`](https://www.github.com/tauri-apps/tauri/commit/113bcd7b684a72eb0f421c663c6aa874197252bb)([#7980](https://www.github.com/tauri-apps/tauri/pull/7980)) In Debian packages, set `root` the owner of control files and package files.
+
+## \[1.4.2]
+
+### Bug Fixes
+
+- [`f552c179`](https://www.github.com/tauri-apps/tauri/commit/f552c1796a61a5cfd51fad6d616bea3164b48a21)([#7998](https://www.github.com/tauri-apps/tauri/pull/7998)) Update the WebView2 offline installer GUIDs to resolve the 404 HTTP response status codes.
+
+## \[1.4.1]
+
+### Bug Fixes
+
+- [`40d34002`](https://www.github.com/tauri-apps/tauri/commit/40d340021c0eab65aa1713807f7564e0698a321e)([#7972](https://www.github.com/tauri-apps/tauri/pull/7972)) The `APPLE_TEAM_ID` environment variable is now required for notarization authentication via Apple ID and app-specific password.
+- [`cdd5516f`](https://www.github.com/tauri-apps/tauri/commit/cdd5516f339ad4345623a1e785c6e2c3a77477f8)([#7956](https://www.github.com/tauri-apps/tauri/pull/7956)) Fixes an app crash on app updates when the product name contained spaces.
+
+## \[1.4.0]
+
+### New Features
+
+- [`4dd4893d`](https://www.github.com/tauri-apps/tauri/commit/4dd4893d7d166ac3a3b6dc2e3bd2540326352a78)([#5950](https://www.github.com/tauri-apps/tauri/pull/5950)) Allow using a resource map instead of a simple array in `BundleSettings::resources_map`.
+
+### Enhancements
+
+- [`764968ab`](https://www.github.com/tauri-apps/tauri/commit/764968ab383ec639e061986bc2411dd44e71b612)([#7398](https://www.github.com/tauri-apps/tauri/pull/7398)) Sign NSIS uninstaller as well.
+- [`2f8881c0`](https://www.github.com/tauri-apps/tauri/commit/2f8881c010fa3493c092ddf3a343df08d7a79fc9)([#7775](https://www.github.com/tauri-apps/tauri/pull/7775)) Read the `APPLE_TEAM_ID` environment variable for macOS notarization arguments.
+- [`cb1d4164`](https://www.github.com/tauri-apps/tauri/commit/cb1d4164e71e29f071b8438d02a7ec86a9fac67b)([#7487](https://www.github.com/tauri-apps/tauri/pull/7487)) On Windows, code sign the application binaries before trying to create the WiX and NSIS bundles to always sign the executables even if no bundle types are enabled.
+
+  On Windows, code sign the sidecar binaries if they are not signed already.
+- [`57f73f1b`](https://www.github.com/tauri-apps/tauri/commit/57f73f1b6a07e690d122d7534a0b3531c8c12c03)([#7486](https://www.github.com/tauri-apps/tauri/pull/7486)) On Windows, NSIS installer will write webview2 installer file to the well-known temp dir instead of the install dir, so we don't pollute the install dir.
+- [`a7777ff4`](https://www.github.com/tauri-apps/tauri/commit/a7777ff485b725f177d08bbc00af607cd8ee8d6d)([#7626](https://www.github.com/tauri-apps/tauri/pull/7626)) Added Bulgarian language support to the NSIS bundler.
+- [`e3bfb014`](https://www.github.com/tauri-apps/tauri/commit/e3bfb01411c3cc5e602c8f961f6cb5c9dd9524e1)([#7776](https://www.github.com/tauri-apps/tauri/pull/7776)) Add `compression` configuration option under `tauri > bundle > windows > nsis`.
+
+### Bug Fixes
+
+- [`46df2c9b`](https://www.github.com/tauri-apps/tauri/commit/46df2c9b917096388695f72ca4c56791fe652ef6)([#7360](https://www.github.com/tauri-apps/tauri/pull/7360)) Fix bundler skipping updater artifacts if `updater` target shows before other updater-enabled targets in the list, see [#7349](https://github.com/tauri-apps/tauri/issues/7349).
+- [`2d35f937`](https://www.github.com/tauri-apps/tauri/commit/2d35f937de59272d26556310155c0b15d849953c)([#7481](https://www.github.com/tauri-apps/tauri/pull/7481)) Fix bundler skipping updater artifacts if only a macOS DMG bundle target is specified.
+- [`dcdbe3eb`](https://www.github.com/tauri-apps/tauri/commit/dcdbe3eb6cc7d8a43caef98dfce71a11a4597644)([#7774](https://www.github.com/tauri-apps/tauri/pull/7774)) Remove extended attributes on the macOS app bundle using `xattr -cr $PATH`.
+- [`dcdbe3eb`](https://www.github.com/tauri-apps/tauri/commit/dcdbe3eb6cc7d8a43caef98dfce71a11a4597644)([#7774](https://www.github.com/tauri-apps/tauri/pull/7774)) Code sign sidecars and frameworks on macOS.
+- [`eba8e131`](https://www.github.com/tauri-apps/tauri/commit/eba8e1315ed7078eb9a9479f9e0072b061067341)([#7386](https://www.github.com/tauri-apps/tauri/pull/7386)) On Windows, fix installation packages not showing correct copyright information.
+- [`32218a6f`](https://www.github.com/tauri-apps/tauri/commit/32218a6f8c1d90c2503e7cbc4523e4ab464ba032)([#7326](https://www.github.com/tauri-apps/tauri/pull/7326)) On Windows, fix NSIS installer identifying a previous NSIS-installed app as WiX-installed app and then fails to uninstall it.
+- [`ca977f4b`](https://www.github.com/tauri-apps/tauri/commit/ca977f4b87c66808b4eac31a6d1925842b4c1570)([#7591](https://www.github.com/tauri-apps/tauri/pull/7591)) On Windows, Fix NSIS uninstaller deleting the wrong application data if the delete the application data checkbox is checked.
+- [`0ae53f41`](https://www.github.com/tauri-apps/tauri/commit/0ae53f413948c7b955e595aa9c6c9e777caa8666)([#7361](https://www.github.com/tauri-apps/tauri/pull/7361)) On Windows, fix NSIS installer showing an error dialog even when the previous version was uninstalled sucessfully.
+- [`09f7f57e`](https://www.github.com/tauri-apps/tauri/commit/09f7f57eeadbf94d8e9e14f3ab2b115a4c4aa473)([#7711](https://www.github.com/tauri-apps/tauri/pull/7711)) On Windows, fix NSIS installer trying to kill itself if the installer file name and the app `productName` are the same.
+- [`6e36ebbf`](https://www.github.com/tauri-apps/tauri/commit/6e36ebbf84dee11a98d8df916c316c7d6f67b2a8)([#7342](https://www.github.com/tauri-apps/tauri/pull/7342)) On Windows, fix NSIS uninstaller failing to remove Start Menu shortcut if `perMachine` mode is used.
+
+### Dependencies
+
+- Upgraded to `tauri-utils@1.5.0`
+- [`a2be88a2`](https://www.github.com/tauri-apps/tauri/commit/a2be88a21db76e9fa063c527031f3849f066eecd)([#7405](https://www.github.com/tauri-apps/tauri/pull/7405)) Removed the `bitness` dependency to speed up compile time.
+
+### Breaking Changes
+
+- [`964d81ff`](https://www.github.com/tauri-apps/tauri/commit/964d81ff01a076516d323546c169b2ba8156e55a)([#7616](https://www.github.com/tauri-apps/tauri/pull/7616)) The macOS notarization now uses `notarytool` as `altool` will be discontinued on November 2023. When authenticating with an API key, the key `.p8` file path must be provided in the `APPLE_API_KEY_PATH` environment variable. To prevent a breaking change, we will try to find the key path in the `altool` default search paths.
+
 ## \[1.3.0]
 
 ### New Features

+ 7 - 0
tooling/bundler/Cargo.toml

@@ -47,6 +47,13 @@ bitness = "0.4"
 winreg = "0.51"
 glob = "0.3"
 
+  [target."cfg(target_os = \"windows\")".dependencies.windows-sys]
+  version = "0.48"
+  features = [
+  "Win32_System_SystemInformation",
+  "Win32_System_Diagnostics_Debug"
+]
+
 [target."cfg(target_os = \"macos\")".dependencies]
 icns = { package = "tauri-icns", version = "0.1" }
 time = { version = "0.3", features = [ "formatting" ] }

+ 28 - 1
tooling/bundler/src/bundle.rs

@@ -43,11 +43,13 @@ pub struct Bundle {
 /// Bundles the project.
 /// Returns the list of paths where the bundles can be found.
 pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
-  let package_types = settings.package_types()?;
+  let mut package_types = settings.package_types()?;
   if package_types.is_empty() {
     return Ok(Vec::new());
   }
 
+  package_types.sort_by_key(|a| a.priority());
+
   let mut bundles: Vec<Bundle> = Vec::new();
 
   let target_os = settings
@@ -61,6 +63,30 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
     warn!("Cross-platform compilation is experimental and does not support all features. Please use a matching host system for full compatibility.");
   }
 
+  #[cfg(target_os = "windows")]
+  {
+    // Sign windows binaries before the bundling step in case neither wix and nsis bundles are enabled
+    for bin in settings.binaries() {
+      let bin_path = settings.binary_path(bin);
+      windows::sign::try_sign(&bin_path, &settings)?;
+    }
+
+    // Sign the sidecar binaries
+    for bin in settings.external_binaries() {
+      let path = bin?;
+      let skip = std::env::var("TAURI_SKIP_SIDECAR_SIGNATURE_CHECK").map_or(false, |v| v == "true");
+
+      if !skip && windows::sign::verify(&path)? {
+        info!(
+          "sidecar at \"{}\" already signed. Skipping...",
+          path.display()
+        )
+      } else {
+        windows::sign::try_sign(&path, &settings)?;
+      }
+    }
+  }
+
   for package_type in &package_types {
     // bundle was already built! e.g. DMG already built .app
     if bundles.iter().any(|b| b.package_type == *package_type) {
@@ -103,6 +129,7 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
             p,
             PackageType::AppImage
               | PackageType::MacOsBundle
+              | PackageType::Dmg
               | PackageType::Nsis
               | PackageType::WindowsMsi
           )

+ 11 - 13
tooling/bundler/src/bundle/common.rs

@@ -8,7 +8,7 @@ use log::debug;
 use std::{
   ffi::OsStr,
   fs::{self, File},
-  io::{self, BufReader, BufWriter},
+  io::{self, BufRead, BufReader, BufWriter},
   path::Path,
   process::{Command, ExitStatus, Output, Stdio},
   sync::{Arc, Mutex},
@@ -160,16 +160,15 @@ impl CommandExt for Command {
     let stdout_lines = Arc::new(Mutex::new(Vec::new()));
     let stdout_lines_ = stdout_lines.clone();
     std::thread::spawn(move || {
-      let mut buf = Vec::new();
+      let mut line = String::new();
       let mut lines = stdout_lines_.lock().unwrap();
       loop {
-        buf.clear();
-        if let Ok(0) = tauri_utils::io::read_line(&mut stdout, &mut buf) {
+        line.clear();
+        if let Ok(0) = stdout.read_line(&mut line) {
           break;
         }
-        debug!(action = "stdout"; "{}", String::from_utf8_lossy(&buf));
-        lines.extend(buf.clone());
-        lines.push(b'\n');
+        debug!(action = "stdout"; "{}", &line[0..line.len() - 1]);
+        lines.extend(line.as_bytes().to_vec());
       }
     });
 
@@ -177,16 +176,15 @@ impl CommandExt for Command {
     let stderr_lines = Arc::new(Mutex::new(Vec::new()));
     let stderr_lines_ = stderr_lines.clone();
     std::thread::spawn(move || {
-      let mut buf = Vec::new();
+      let mut line = String::new();
       let mut lines = stderr_lines_.lock().unwrap();
       loop {
-        buf.clear();
-        if let Ok(0) = tauri_utils::io::read_line(&mut stderr, &mut buf) {
+        line.clear();
+        if let Ok(0) = stderr.read_line(&mut line) {
           break;
         }
-        debug!(action = "stderr"; "{}", String::from_utf8_lossy(&buf));
-        lines.extend(buf.clone());
-        lines.push(b'\n');
+        debug!(action = "stderr"; "{}", &line[0..line.len() - 1]);
+        lines.extend(line.as_bytes().to_vec());
       }
     });
 

+ 12 - 2
tooling/bundler/src/bundle/linux/debian.rs

@@ -380,10 +380,20 @@ fn create_tar_from_dir<P: AsRef<Path>, W: Write>(src_dir: P, dest_file: W) -> cr
     }
     let dest_path = src_path.strip_prefix(src_dir)?;
     if entry.file_type().is_dir() {
-      tar_builder.append_dir(dest_path, src_path)?;
+      let stat = fs::metadata(src_path)?;
+      let mut header = tar::Header::new_gnu();
+      header.set_metadata(&stat);
+      header.set_uid(0);
+      header.set_gid(0);
+      tar_builder.append_data(&mut header, dest_path, &mut io::empty())?;
     } else {
       let mut src_file = fs::File::open(src_path)?;
-      tar_builder.append_file(dest_path, &mut src_file)?;
+      let stat = src_file.metadata()?;
+      let mut header = tar::Header::new_gnu();
+      header.set_metadata(&stat);
+      header.set_uid(0);
+      header.set_gid(0);
+      tar_builder.append_data(&mut header, dest_path, &mut src_file)?;
     }
   }
   let dest_file = tar_builder.into_inner()?;

+ 81 - 18
tooling/bundler/src/bundle/macos/app.rs

@@ -23,9 +23,9 @@
 // files into the `Contents` directory of the bundle.
 
 use super::{
-  super::common,
+  super::common::{self, CommandExt},
   icon::create_icns_file,
-  sign::{notarize, notarize_auth_args, sign},
+  sign::{notarize, notarize_auth, sign, NotarizeAuthError, SignTarget},
 };
 use crate::Settings;
 
@@ -33,8 +33,10 @@ use anyhow::Context;
 use log::{info, warn};
 
 use std::{
+  ffi::OsStr,
   fs,
   path::{Path, PathBuf},
+  process::Command,
 };
 
 /// Bundles the project.
@@ -65,6 +67,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
 
   let resources_dir = bundle_directory.join("Resources");
   let bin_dir = bundle_directory.join("MacOS");
+  let mut sign_paths = Vec::new();
 
   let bundle_icon_file: Option<PathBuf> =
     { create_icns_file(&resources_dir, settings).with_context(|| "Failed to create app icon")? };
@@ -72,27 +75,63 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
   create_info_plist(&bundle_directory, bundle_icon_file, settings)
     .with_context(|| "Failed to create Info.plist")?;
 
-  copy_frameworks_to_bundle(&bundle_directory, settings)
+  let framework_paths = copy_frameworks_to_bundle(&bundle_directory, settings)
     .with_context(|| "Failed to bundle frameworks")?;
+  sign_paths.extend(
+    framework_paths
+      .into_iter()
+      .filter(|p| {
+        let ext = p.extension();
+        ext == Some(OsStr::new("framework")) || ext == Some(OsStr::new("dylib"))
+      })
+      .map(|path| SignTarget {
+        path,
+        is_an_executable: false,
+      }),
+  );
 
   settings.copy_resources(&resources_dir)?;
 
-  settings
+  let bin_paths = settings
     .copy_binaries(&bin_dir)
     .with_context(|| "Failed to copy external binaries")?;
+  sign_paths.extend(bin_paths.into_iter().map(|path| SignTarget {
+    path,
+    is_an_executable: true,
+  }));
 
-  copy_binaries_to_bundle(&bundle_directory, settings)?;
+  let bin_paths = copy_binaries_to_bundle(&bundle_directory, settings)?;
+  sign_paths.extend(bin_paths.into_iter().map(|path| SignTarget {
+    path,
+    is_an_executable: true,
+  }));
 
   if let Some(identity) = &settings.macos().signing_identity {
+    // Sign frameworks and sidecar binaries first, per apple, signing must be done inside out
+    // https://developer.apple.com/forums/thread/701514
+    sign_paths.push(SignTarget {
+      path: app_bundle_path.clone(),
+      is_an_executable: true,
+    });
+
+    // Remove extra attributes, which could cause codesign to fail
+    // https://developer.apple.com/library/archive/qa/qa1940/_index.html
+    remove_extra_attr(&app_bundle_path)?;
+
     // sign application
-    sign(app_bundle_path.clone(), identity, settings, true)?;
+    sign(sign_paths, identity, settings)?;
+
     // notarization is required for distribution
-    match notarize_auth_args() {
-      Ok(args) => {
-        notarize(app_bundle_path.clone(), args, settings)?;
+    match notarize_auth() {
+      Ok(auth) => {
+        notarize(app_bundle_path.clone(), auth, settings)?;
       }
       Err(e) => {
-        warn!("skipping app notarization, {}", e.to_string());
+        if matches!(e, NotarizeAuthError::MissingTeamId) {
+          return Err(anyhow::anyhow!("{e}").into());
+        } else {
+          warn!("skipping app notarization, {}", e.to_string());
+        }
       }
     }
   }
@@ -100,15 +139,30 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
   Ok(vec![app_bundle_path])
 }
 
+fn remove_extra_attr(app_bundle_path: &Path) -> crate::Result<()> {
+  Command::new("xattr")
+    .arg("-cr")
+    .arg(app_bundle_path)
+    .output_ok()
+    .context("failed to remove extra attributes from app bundle")?;
+  Ok(())
+}
+
 // Copies the app's binaries to the bundle.
-fn copy_binaries_to_bundle(bundle_directory: &Path, settings: &Settings) -> crate::Result<()> {
+fn copy_binaries_to_bundle(
+  bundle_directory: &Path,
+  settings: &Settings,
+) -> crate::Result<Vec<PathBuf>> {
+  let mut paths = Vec::new();
   let dest_dir = bundle_directory.join("MacOS");
   for bin in settings.binaries() {
     let bin_path = settings.binary_path(bin);
-    common::copy_file(&bin_path, &dest_dir.join(bin.name()))
+    let dest_path = dest_dir.join(bin.name());
+    common::copy_file(&bin_path, &dest_path)
       .with_context(|| format!("Failed to copy binary from {:?}", bin_path))?;
+    paths.push(dest_path);
   }
-  Ok(())
+  Ok(paths)
 }
 
 // Creates the Info.plist file.
@@ -247,7 +301,12 @@ fn copy_framework_from(dest_dir: &Path, framework: &str, src_dir: &Path) -> crat
 }
 
 // Copies the macOS application bundle frameworks to the .app
-fn copy_frameworks_to_bundle(bundle_directory: &Path, settings: &Settings) -> crate::Result<()> {
+fn copy_frameworks_to_bundle(
+  bundle_directory: &Path,
+  settings: &Settings,
+) -> crate::Result<Vec<PathBuf>> {
+  let mut paths = Vec::new();
+
   let frameworks = settings
     .macos()
     .frameworks
@@ -255,7 +314,7 @@ fn copy_frameworks_to_bundle(bundle_directory: &Path, settings: &Settings) -> cr
     .cloned()
     .unwrap_or_default();
   if frameworks.is_empty() {
-    return Ok(());
+    return Ok(paths);
   }
   let dest_dir = bundle_directory.join("Frameworks");
   fs::create_dir_all(bundle_directory)
@@ -266,7 +325,9 @@ fn copy_frameworks_to_bundle(bundle_directory: &Path, settings: &Settings) -> cr
       let src_name = src_path
         .file_name()
         .expect("Couldn't get framework filename");
-      common::copy_dir(&src_path, &dest_dir.join(src_name))?;
+      let dest_path = dest_dir.join(src_name);
+      common::copy_dir(&src_path, &dest_path)?;
+      paths.push(dest_path);
       continue;
     } else if framework.ends_with(".dylib") {
       let src_path = PathBuf::from(framework);
@@ -277,7 +338,9 @@ fn copy_frameworks_to_bundle(bundle_directory: &Path, settings: &Settings) -> cr
         )));
       }
       let src_name = src_path.file_name().expect("Couldn't get library filename");
-      common::copy_file(&src_path, &dest_dir.join(src_name))?;
+      let dest_path = dest_dir.join(src_name);
+      common::copy_file(&src_path, &dest_path)?;
+      paths.push(dest_path);
       continue;
     } else if framework.contains('/') {
       return Err(crate::Error::GenericError(format!(
@@ -304,5 +367,5 @@ fn copy_frameworks_to_bundle(bundle_directory: &Path, settings: &Settings) -> cr
       framework
     )));
   }
-  Ok(())
+  Ok(paths)
 }

+ 8 - 1
tooling/bundler/src/bundle/macos/dmg.rs

@@ -153,7 +153,14 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<
 
   // Sign DMG if needed
   if let Some(identity) = &settings.macos().signing_identity {
-    super::sign::sign(dmg_path.clone(), identity, settings, false)?;
+    super::sign::sign(
+      vec![super::sign::SignTarget {
+        path: dmg_path.clone(),
+        is_an_executable: false,
+      }],
+      identity,
+      settings,
+    )?;
   }
 
   Ok(Bundled {

+ 189 - 131
tooling/bundler/src/bundle/macos/sign.rs

@@ -3,13 +3,19 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use std::ffi::OsString;
-use std::{fs::File, io::prelude::*, path::PathBuf, process::Command};
+use std::{
+  env::{var, var_os},
+  ffi::OsString,
+  fs::File,
+  io::prelude::*,
+  path::PathBuf,
+  process::Command,
+};
 
 use crate::{bundle::common::CommandExt, Settings};
 use anyhow::Context;
 use log::info;
-use regex::Regex;
+use serde::Deserialize;
 
 const KEYCHAIN_ID: &str = "tauri-build.keychain";
 const KEYCHAIN_PWD: &str = "tauri-build";
@@ -138,17 +144,17 @@ pub fn delete_keychain() {
     .output_ok();
 }
 
-pub fn sign(
-  path_to_sign: PathBuf,
-  identity: &str,
-  settings: &Settings,
-  is_an_executable: bool,
-) -> crate::Result<()> {
-  info!(action = "Signing"; "{} with identity \"{}\"", path_to_sign.display(), identity);
+pub struct SignTarget {
+  pub path: PathBuf,
+  pub is_an_executable: bool,
+}
+
+pub fn sign(targets: Vec<SignTarget>, identity: &str, settings: &Settings) -> crate::Result<()> {
+  info!(action = "Signing"; "with identity \"{}\"", identity);
 
   let setup_keychain = if let (Some(certificate_encoded), Some(certificate_password)) = (
-    std::env::var_os("APPLE_CERTIFICATE"),
-    std::env::var_os("APPLE_CERTIFICATE_PASSWORD"),
+    var_os("APPLE_CERTIFICATE"),
+    var_os("APPLE_CERTIFICATE_PASSWORD"),
   ) {
     // setup keychain allow you to import your certificate
     // for CI build
@@ -158,20 +164,24 @@ pub fn sign(
     false
   };
 
-  let res = try_sign(
-    path_to_sign,
-    identity,
-    settings,
-    is_an_executable,
-    setup_keychain,
-  );
+  info!("Signing app bundle...");
+
+  for target in targets {
+    try_sign(
+      target.path,
+      identity,
+      settings,
+      target.is_an_executable,
+      setup_keychain,
+    )?;
+  }
 
   if setup_keychain {
     // delete the keychain again after signing
     delete_keychain();
   }
 
-  res
+  Ok(())
 }
 
 fn try_sign(
@@ -181,6 +191,8 @@ fn try_sign(
   is_an_executable: bool,
   tauri_keychain: bool,
 ) -> crate::Result<()> {
+  info!(action = "Signing"; "{}", path_to_sign.display());
+
   let mut args = vec!["--force", "-s", identity];
 
   if tauri_keychain {
@@ -199,26 +211,27 @@ fn try_sign(
     args.push("runtime");
   }
 
-  if path_to_sign.is_dir() {
-    args.push("--deep");
-  }
-
   Command::new("codesign")
     .args(args)
-    .arg(path_to_sign.to_string_lossy().to_string())
+    .arg(path_to_sign)
     .output_ok()
     .context("failed to sign app")?;
 
   Ok(())
 }
 
+#[derive(Deserialize)]
+struct NotarytoolSubmitOutput {
+  id: String,
+  status: String,
+  message: String,
+}
+
 pub fn notarize(
   app_bundle_path: PathBuf,
-  auth_args: Vec<String>,
+  auth: NotarizeAuth,
   settings: &Settings,
 ) -> crate::Result<()> {
-  let identifier = settings.bundle_identifier();
-
   let bundle_stem = app_bundle_path
     .file_stem()
     .expect("failed to get bundle filename");
@@ -249,58 +262,70 @@ pub fn notarize(
 
   // sign the zip file
   if let Some(identity) = &settings.macos().signing_identity {
-    sign(zip_path.clone(), identity, settings, false)?;
+    sign(
+      vec![SignTarget {
+        path: zip_path.clone(),
+        is_an_executable: false,
+      }],
+      identity,
+      settings,
+    )?;
   };
 
-  let mut notarize_args = vec![
-    "altool",
-    "--notarize-app",
-    "-f",
+  let notarize_args = vec![
+    "notarytool",
+    "submit",
     zip_path
       .to_str()
       .expect("failed to convert zip_path to string"),
-    "--primary-bundle-id",
-    identifier,
+    "--wait",
+    "--output-format",
+    "json",
   ];
 
-  if let Some(provider_short_name) = &settings.macos().provider_short_name {
-    notarize_args.push("--asc-provider");
-    notarize_args.push(provider_short_name);
-  }
-
   info!(action = "Notarizing"; "{}", app_bundle_path.display());
 
   let output = Command::new("xcrun")
     .args(notarize_args)
-    .args(auth_args.clone())
+    .notarytool_args(&auth)
     .output_ok()
     .context("failed to upload app to Apple's notarization servers.")?;
 
-  // combine both stdout and stderr to support macOS below 10.15
-  let mut notarize_response = std::str::from_utf8(&output.stdout)?.to_string();
-  notarize_response.push('\n');
-  notarize_response.push_str(std::str::from_utf8(&output.stderr)?);
-  notarize_response.push('\n');
-  if let Some(uuid) = Regex::new(r"\nRequestUUID = (.+?)\n")?
-    .captures_iter(&notarize_response)
-    .next()
-  {
-    info!("notarization started; waiting for Apple response...");
-
-    let uuid = uuid[1].to_string();
-    get_notarization_status(uuid, auth_args)?;
-    staple_app(app_bundle_path.clone())?;
-  } else {
-    return Err(
-      anyhow::anyhow!(
-        "failed to parse RequestUUID from upload output. {}",
-        notarize_response
-      )
-      .into(),
-    );
+  if !output.status.success() {
+    return Err(anyhow::anyhow!("failed to notarize app").into());
   }
 
-  Ok(())
+  let output_str = String::from_utf8_lossy(&output.stdout);
+  if let Ok(submit_output) = serde_json::from_str::<NotarytoolSubmitOutput>(&output_str) {
+    let log_message = format!(
+      "Finished with status {} for id {} ({})",
+      submit_output.status, submit_output.id, submit_output.message
+    );
+    if submit_output.status == "Accepted" {
+      log::info!(action = "Notarizing"; "{}", log_message);
+      staple_app(app_bundle_path)?;
+      Ok(())
+    } else {
+      if let Ok(output) = Command::new("xcrun")
+        .args(["notarytool", "log"])
+        .arg(&submit_output.id)
+        .notarytool_args(&auth)
+        .output_ok()
+      {
+        Err(
+          anyhow::anyhow!(
+            "{log_message}\nLog:\n{}",
+            String::from_utf8_lossy(&output.stdout)
+          )
+          .into(),
+        )
+      } else {
+        Err(anyhow::anyhow!("{log_message}").into())
+      }
+    }
+  } else {
+    Err(anyhow::anyhow!("failed to parse notarytool output as JSON: `{output_str}`").into())
+  }
 }
 
 fn staple_app(mut app_bundle_path: PathBuf) -> crate::Result<()> {
@@ -322,83 +347,116 @@ fn staple_app(mut app_bundle_path: PathBuf) -> crate::Result<()> {
   Ok(())
 }
 
-fn get_notarization_status(uuid: String, auth_args: Vec<String>) -> crate::Result<()> {
-  std::thread::sleep(std::time::Duration::from_secs(10));
-  let result = Command::new("xcrun")
-    .args(vec!["altool", "--notarization-info", &uuid])
-    .args(auth_args.clone())
-    .output_ok();
+pub enum NotarizeAuth {
+  AppleId {
+    apple_id: OsString,
+    password: OsString,
+    team_id: OsString,
+  },
+  ApiKey {
+    key: OsString,
+    key_path: PathBuf,
+    issuer: OsString,
+  },
+}
 
-  if let Ok(output) = result {
-    // combine both stdout and stderr to support macOS below 10.15
-    let mut notarize_status = std::str::from_utf8(&output.stdout)?.to_string();
-    notarize_status.push('\n');
-    notarize_status.push_str(std::str::from_utf8(&output.stderr)?);
-    notarize_status.push('\n');
-    if let Some(status) = Regex::new(r"\n *Status: (.+?)\n")?
-      .captures_iter(&notarize_status)
-      .next()
-    {
-      let status = status[1].to_string();
-      if status == "in progress" {
-        get_notarization_status(uuid, auth_args)
-      } else if status == "invalid" {
-        Err(
-          anyhow::anyhow!(format!(
-            "Apple failed to notarize your app. {}",
-            notarize_status
-          ))
-          .into(),
-        )
-      } else if status != "success" {
-        Err(
-          anyhow::anyhow!(format!(
-            "Unknown notarize status {}. {}",
-            status, notarize_status
-          ))
-          .into(),
-        )
-      } else {
-        Ok(())
-      }
-    } else {
-      get_notarization_status(uuid, auth_args)
+pub trait NotarytoolCmdExt {
+  fn notarytool_args(&mut self, auth: &NotarizeAuth) -> &mut Self;
+}
+
+impl NotarytoolCmdExt for Command {
+  fn notarytool_args(&mut self, auth: &NotarizeAuth) -> &mut Self {
+    match auth {
+      NotarizeAuth::AppleId {
+        apple_id,
+        password,
+        team_id,
+      } => self
+        .arg("--apple-id")
+        .arg(apple_id)
+        .arg("--password")
+        .arg(password)
+        .arg("--team-id")
+        .arg(team_id),
+      NotarizeAuth::ApiKey {
+        key,
+        key_path,
+        issuer,
+      } => self
+        .arg("--key-id")
+        .arg(key)
+        .arg("--key")
+        .arg(key_path)
+        .arg("--issuer")
+        .arg(issuer),
     }
-  } else {
-    get_notarization_status(uuid, auth_args)
   }
 }
 
-pub fn notarize_auth_args() -> crate::Result<Vec<String>> {
+#[derive(Debug, thiserror::Error)]
+pub enum NotarizeAuthError {
+  #[error(
+    "The team ID is now required for notarization with app-specific password as authentication. Please set the `APPLE_TEAM_ID` environment variable. You can find the team ID in https://developer.apple.com/account#MembershipDetailsCard."
+  )]
+  MissingTeamId,
+  #[error(transparent)]
+  Anyhow(#[from] anyhow::Error),
+}
+
+pub fn notarize_auth() -> Result<NotarizeAuth, NotarizeAuthError> {
   match (
-    std::env::var_os("APPLE_ID"),
-    std::env::var_os("APPLE_PASSWORD"),
+    var_os("APPLE_ID"),
+    var_os("APPLE_PASSWORD"),
+    var_os("APPLE_TEAM_ID"),
   ) {
-    (Some(apple_id), Some(apple_password)) => {
-      let apple_id = apple_id
-        .to_str()
-        .expect("failed to convert APPLE_ID to string")
-        .to_string();
-      let apple_password = apple_password
-        .to_str()
-        .expect("failed to convert APPLE_PASSWORD to string")
-        .to_string();
-      Ok(vec![
-        "-u".to_string(),
-        apple_id,
-        "-p".to_string(),
-        apple_password,
-      ])
-    }
+    (Some(apple_id), Some(password), Some(team_id)) => Ok(NotarizeAuth::AppleId {
+      apple_id,
+      password,
+      team_id,
+    }),
+    (Some(_apple_id), Some(_password), None) => Err(NotarizeAuthError::MissingTeamId),
     _ => {
-      match (std::env::var_os("APPLE_API_KEY"), std::env::var_os("APPLE_API_ISSUER")) {
-        (Some(api_key), Some(api_issuer)) => {
-          let api_key = api_key.to_str().expect("failed to convert APPLE_API_KEY to string").to_string();
-          let api_issuer = api_issuer.to_str().expect("failed to convert APPLE_API_ISSUER to string").to_string();
-          Ok(vec!["--apiKey".to_string(), api_key, "--apiIssuer".to_string(), api_issuer])
+      match (var_os("APPLE_API_KEY"), var_os("APPLE_API_ISSUER"), var("APPLE_API_KEY_PATH")) {
+        (Some(key), Some(issuer), Ok(key_path)) => {
+          Ok(NotarizeAuth::ApiKey { key, key_path: key_path.into(), issuer })
         },
-        _ => Err(anyhow::anyhow!("no APPLE_ID & APPLE_PASSWORD or APPLE_API_KEY & APPLE_API_ISSUER environment variables found").into())
+        (Some(key), Some(issuer), Err(_)) => {
+          let mut api_key_file_name = OsString::from("AuthKey_");
+          api_key_file_name.push(&key);
+          api_key_file_name.push(".p8");
+          let mut key_path = None;
+
+          let mut search_paths = vec!["./private_keys".into()];
+          if let Some(home_dir) = dirs_next::home_dir() {
+            search_paths.push(home_dir.join("private_keys"));
+            search_paths.push(home_dir.join(".private_keys"));
+            search_paths.push(home_dir.join(".appstoreconnect").join("private_keys"));
+          }
+
+          for folder in search_paths {
+            if let Some(path) = find_api_key(folder, &api_key_file_name) {
+              key_path = Some(path);
+              break;
+            }
+          }
+
+          if let Some(key_path) = key_path {
+          Ok(NotarizeAuth::ApiKey { key, key_path, issuer })
+          } else {
+            Err(anyhow::anyhow!("could not find API key file. Please set the APPLE_API_KEY_PATH environment variables to the path to the {api_key_file_name:?} file").into())
+          }
+        }
+        _ => Err(anyhow::anyhow!("no APPLE_ID & APPLE_PASSWORD & APPLE_TEAM_ID or APPLE_API_KEY & APPLE_API_ISSUER & APPLE_API_KEY_PATH environment variables found").into())
       }
     }
   }
 }
+
+fn find_api_key(folder: PathBuf, file_name: &OsString) -> Option<PathBuf> {
+  let path = folder.join(file_name);
+  if path.exists() {
+    Some(path)
+  } else {
+    None
+  }
+}

+ 49 - 11
tooling/bundler/src/bundle/settings.rs

@@ -7,7 +7,7 @@ use super::category::AppCategory;
 use crate::bundle::{common, platform::target_triple};
 pub use tauri_utils::config::WebviewInstallMode;
 use tauri_utils::{
-  config::{BundleType, FileAssociation, NSISInstallerMode},
+  config::{BundleType, FileAssociation, NSISInstallerMode, NsisCompression},
   resources::{external_binaries, ResourcePaths},
 };
 
@@ -93,6 +93,26 @@ impl PackageType {
   pub fn all() -> &'static [PackageType] {
     ALL_PACKAGE_TYPES
   }
+
+  /// Gets a number representing priority which used to sort package types
+  /// in an order that guarantees that if a certain package type
+  /// depends on another (like Dmg depending on MacOsBundle), the dependency
+  /// will be built first
+  ///
+  /// The lower the number, the higher the priority
+  pub fn priority(&self) -> u32 {
+    match self {
+      PackageType::MacOsBundle => 0,
+      PackageType::IosBundle => 0,
+      PackageType::WindowsMsi => 0,
+      PackageType::Nsis => 0,
+      PackageType::Deb => 0,
+      PackageType::Rpm => 0,
+      PackageType::AppImage => 0,
+      PackageType::Dmg => 1,
+      PackageType::Updater => 2,
+    }
+  }
 }
 
 const ALL_PACKAGE_TYPES: &[PackageType] = &[
@@ -289,6 +309,8 @@ pub struct NsisSettings {
   /// Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not.
   /// By default the OS language is selected, with a fallback to the first language in the `languages` array.
   pub display_language_selector: bool,
+  /// Set compression algorithm used to compress files in the installer.
+  pub compression: Option<NsisCompression>,
 }
 
 /// The Windows bundle settings.
@@ -358,6 +380,12 @@ pub struct BundleSettings {
   ///
   /// supports glob patterns.
   pub resources: Option<Vec<String>>,
+  /// The app's resources to bundle. Takes precedence over `Self::resources` when specified.
+  ///
+  /// Maps each resource path to its target directory in the bundle resources directory.
+  ///
+  /// Supports glob patterns.
+  pub resources_map: Option<HashMap<String, String>>,
   /// the app's copyright.
   pub copyright: Option<String>,
   /// the app's category.
@@ -709,9 +737,14 @@ impl Settings {
   /// Returns an iterator over the resource files to be included in this
   /// bundle.
   pub fn resource_files(&self) -> ResourcePaths<'_> {
-    match self.bundle_settings.resources {
-      Some(ref paths) => ResourcePaths::new(paths.as_slice(), true),
-      None => ResourcePaths::new(&[], true),
+    match (
+      &self.bundle_settings.resources,
+      &self.bundle_settings.resources_map,
+    ) {
+      (Some(paths), None) => ResourcePaths::new(paths.as_slice(), true),
+      (None, Some(map)) => ResourcePaths::from_map(map, true),
+      (Some(_), Some(_)) => panic!("cannot use both `resources` and `resources_map`"),
+      (None, None) => ResourcePaths::new(&[], true),
     }
   }
 
@@ -725,7 +758,11 @@ impl Settings {
   }
 
   /// Copies external binaries to a path.
-  pub fn copy_binaries(&self, path: &Path) -> crate::Result<()> {
+  ///
+  /// Returns the list of destination paths.
+  pub fn copy_binaries(&self, path: &Path) -> crate::Result<Vec<PathBuf>> {
+    let mut paths = Vec::new();
+
     for src in self.external_binaries() {
       let src = src?;
       let dest = path.join(
@@ -735,17 +772,18 @@ impl Settings {
           .to_string_lossy()
           .replace(&format!("-{}", self.target), ""),
       );
-      common::copy_file(&src, dest)?;
+      common::copy_file(&src, &dest)?;
+      paths.push(dest);
     }
-    Ok(())
+    Ok(paths)
   }
 
   /// Copies resources to a path.
   pub fn copy_resources(&self, path: &Path) -> crate::Result<()> {
-    for src in self.resource_files() {
-      let src = src?;
-      let dest = path.join(tauri_utils::resources::resource_relpath(&src));
-      common::copy_file(&src, dest)?;
+    for resource in self.resource_files().iter() {
+      let resource = resource?;
+      let dest = path.join(resource.target());
+      common::copy_file(resource.path(), dest)?;
     }
     Ok(())
   }

+ 11 - 1
tooling/bundler/src/bundle/updater_bundle.rs

@@ -238,12 +238,22 @@ fn create_tar(src_dir: &Path, dest_path: &Path) -> crate::Result<PathBuf> {
   let gzip_encoder = libflate::gzip::Encoder::new(dest_file)?;
 
   let gzip_encoder = create_tar_from_src(src_dir, gzip_encoder)?;
+
   let mut dest_file = gzip_encoder.finish().into_result()?;
   dest_file.flush()?;
   Ok(dest_path.to_owned())
 }
 
-#[cfg(not(target_os = "windows"))]
+#[cfg(target_os = "macos")]
+fn create_tar_from_src<P: AsRef<Path>, W: Write>(src_dir: P, dest_file: W) -> crate::Result<W> {
+  let src_dir = src_dir.as_ref();
+  let mut builder = tar::Builder::new(dest_file);
+  builder.follow_symlinks(false);
+  builder.append_dir_all(src_dir.file_name().expect("Path has no file_name"), src_dir)?;
+  builder.into_inner().map_err(Into::into)
+}
+
+#[cfg(target_os = "linux")]
 fn create_tar_from_src<P: AsRef<Path>, W: Write>(src_dir: P, dest_file: W) -> crate::Result<W> {
   let src_dir = src_dir.as_ref();
   let mut tar_builder = tar::Builder::new(dest_file);

+ 20 - 24
tooling/bundler/src/bundle/windows/msi/wix.rs

@@ -7,10 +7,13 @@ use crate::bundle::{
   common::CommandExt,
   path_utils::{copy_file, FileOpts},
   settings::Settings,
-  windows::util::{
-    download, download_and_verify, extract_zip, try_sign, HashAlgorithm, WEBVIEW2_BOOTSTRAPPER_URL,
-    WEBVIEW2_X64_INSTALLER_GUID, WEBVIEW2_X86_INSTALLER_GUID, WIX_OUTPUT_FOLDER_NAME,
-    WIX_UPDATER_OUTPUT_FOLDER_NAME,
+  windows::{
+    sign::try_sign,
+    util::{
+      download, download_and_verify, extract_zip, HashAlgorithm, WEBVIEW2_BOOTSTRAPPER_URL,
+      WEBVIEW2_X64_OFFLINE_INSTALLER_GUID, WEBVIEW2_X86_OFFLINE_INSTALLER_GUID,
+      WIX_OUTPUT_FOLDER_NAME, WIX_UPDATER_OUTPUT_FOLDER_NAME,
+    },
   },
 };
 use anyhow::{bail, Context};
@@ -25,8 +28,7 @@ use std::{
   path::{Path, PathBuf},
   process::Command,
 };
-use tauri_utils::display_path;
-use tauri_utils::{config::WebviewInstallMode, resources::resource_relpath};
+use tauri_utils::{config::WebviewInstallMode, display_path};
 use uuid::Uuid;
 
 // URLS for the WIX toolchain.  Can be used for cross-platform compilation.
@@ -87,7 +89,7 @@ struct ResourceFile {
   /// the id to use on the WIX XML.
   id: String,
   /// the file path.
-  path: String,
+  path: PathBuf,
 }
 
 /// A resource directory to bundle with WIX.
@@ -121,7 +123,7 @@ impl ResourceDirectory {
           r#"<Component Id="{id}" Guid="{guid}" Win64="$(var.Win64)" KeyPath="yes"><File Id="PathFile_{id}" Source="{path}" /></Component>"#,
           id = file.id,
           guid = file.guid,
-          path = file.path
+          path = file.path.display()
         ).as_str()
       );
     }
@@ -408,8 +410,6 @@ pub fn build_wix_app_installer(
     .ok_or_else(|| anyhow::anyhow!("Failed to get main binary"))?;
   let app_exe_source = settings.binary_path(main_binary);
 
-  try_sign(&app_exe_source, settings)?;
-
   let output_path = settings.project_out_directory().join("wix").join(arch);
 
   if output_path.exists() {
@@ -485,9 +485,9 @@ pub fn build_wix_app_installer(
     }
     WebviewInstallMode::OfflineInstaller { silent: _ } => {
       let guid = if arch == "x64" {
-        WEBVIEW2_X64_INSTALLER_GUID
+        WEBVIEW2_X64_OFFLINE_INSTALLER_GUID
       } else {
-        WEBVIEW2_X86_INSTALLER_GUID
+        WEBVIEW2_X86_OFFLINE_INSTALLER_GUID
       };
       let offline_installer_path = dirs_next::cache_dir()
         .unwrap()
@@ -914,15 +914,11 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
 
   let mut added_resources = Vec::new();
 
-  for src in settings.resource_files() {
-    let src = src?;
-
-    let resource_path = cwd
-      .join(src.clone())
-      .into_os_string()
-      .into_string()
-      .expect("failed to read resource path");
+  for resource in settings.resource_files().iter() {
+    let resource = resource?;
 
+    let src = cwd.join(resource.path());
+    let resource_path = dunce::simplified(&src).to_path_buf();
     // In some glob resource paths like `assets/**/*` a file might appear twice
     // because the `tauri_utils::resources::ResourcePaths` iterator also reads a directory
     // when it finds one. So we must check it before processing the file.
@@ -935,11 +931,11 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
     let resource_entry = ResourceFile {
       id: format!("I{}", Uuid::new_v4().as_simple()),
       guid: Uuid::new_v4().to_string(),
-      path: resource_path,
+      path: resource_path.clone(),
     };
 
     // split the resource path directories
-    let target_path = resource_relpath(&src);
+    let target_path = resource.target();
     let components_count = target_path.components().count();
     let directories = target_path
       .components()
@@ -1004,7 +1000,7 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
   let out_dir = settings.project_out_directory();
   for dll in glob::glob(out_dir.join("*.dll").to_string_lossy().to_string().as_str())? {
     let path = dll?;
-    let resource_path = path.to_string_lossy().into_owned();
+    let resource_path = dunce::simplified(&path);
     let relative_path = path
       .strip_prefix(out_dir)
       .unwrap()
@@ -1014,7 +1010,7 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
       dlls.push(ResourceFile {
         id: format!("I{}", Uuid::new_v4().as_simple()),
         guid: Uuid::new_v4().to_string(),
-        path: resource_path,
+        path: resource_path.to_path_buf(),
       });
     }
   }

+ 40 - 30
tooling/bundler/src/bundle/windows/nsis.rs

@@ -3,14 +3,14 @@
 // SPDX-License-Identifier: MIT
 
 #[cfg(target_os = "windows")]
-use crate::bundle::windows::util::try_sign;
+use crate::bundle::windows::sign::{sign_command, try_sign};
 use crate::{
   bundle::{
     common::CommandExt,
     windows::util::{
       download, download_and_verify, extract_zip, HashAlgorithm, NSIS_OUTPUT_FOLDER_NAME,
-      NSIS_UPDATER_OUTPUT_FOLDER_NAME, WEBVIEW2_BOOTSTRAPPER_URL, WEBVIEW2_X64_INSTALLER_GUID,
-      WEBVIEW2_X86_INSTALLER_GUID,
+      NSIS_UPDATER_OUTPUT_FOLDER_NAME, WEBVIEW2_BOOTSTRAPPER_URL,
+      WEBVIEW2_X64_OFFLINE_INSTALLER_GUID, WEBVIEW2_X86_OFFLINE_INSTALLER_GUID,
     },
   },
   Settings,
@@ -20,10 +20,7 @@ use tauri_utils::display_path;
 use anyhow::Context;
 use handlebars::{to_json, Handlebars};
 use log::{info, warn};
-use tauri_utils::{
-  config::{NSISInstallerMode, WebviewInstallMode},
-  resources::resource_relpath,
-};
+use tauri_utils::config::{NSISInstallerMode, NsisCompression, WebviewInstallMode};
 
 use std::{
   collections::{BTreeMap, HashMap},
@@ -40,8 +37,8 @@ const NSIS_URL: &str =
 const NSIS_SHA1: &str = "057e83c7d82462ec394af76c87d06733605543d4";
 const NSIS_APPLICATIONID_URL: &str = "https://github.com/tauri-apps/binary-releases/releases/download/nsis-plugins-v0/NSIS-ApplicationID.zip";
 const NSIS_TAURI_UTILS: &str =
-  "https://github.com/tauri-apps/nsis-tauri-utils/releases/download/nsis_tauri_utils-v0.1.1/nsis_tauri_utils.dll";
-const NSIS_TAURI_UTILS_SHA1: &str = "A21C67CF5AB6D4274AFFF0D68CFCE680D213DDC7";
+  "https://github.com/tauri-apps/nsis-tauri-utils/releases/download/nsis_tauri_utils-v0.2.1/nsis_tauri_utils.dll";
+const NSIS_TAURI_UTILS_SHA1: &str = "53A7CFAEB6A4A9653D6D5FBFF02A3C3B8720130A";
 
 #[cfg(target_os = "windows")]
 const NSIS_REQUIRED_FILES: &[&str] = &[
@@ -160,17 +157,6 @@ fn build_nsis_app_installer(
 
   info!("Target: {}", arch);
 
-  #[cfg(target_os = "windows")]
-  {
-    let main_binary = settings
-      .binaries()
-      .iter()
-      .find(|bin| bin.main())
-      .ok_or_else(|| anyhow::anyhow!("Failed to get main binary"))?;
-    let app_exe_source = settings.binary_path(main_binary);
-    try_sign(&app_exe_source, settings)?;
-  }
-
   #[cfg(not(target_os = "windows"))]
   info!("Code signing is currently only supported on Windows hosts, skipping...");
 
@@ -201,6 +187,18 @@ fn build_nsis_app_installer(
   data.insert("short_description", to_json(settings.short_description()));
   data.insert("copyright", to_json(settings.copyright_string()));
 
+  // Code signing is currently only supported on Windows hosts
+  #[cfg(target_os = "windows")]
+  if settings.can_sign() {
+    data.insert(
+      "uninstaller_sign_cmd",
+      to_json(format!(
+        "{:?}",
+        sign_command("%1", &settings.sign_params())?.0
+      )),
+    );
+  }
+
   let version = settings.version_string();
   data.insert("version", to_json(version));
   data.insert(
@@ -244,6 +242,15 @@ fn build_nsis_app_installer(
       );
     }
 
+    data.insert(
+      "compression",
+      to_json(match &nsis.compression.unwrap_or(NsisCompression::Lzma) {
+        NsisCompression::Zlib => "zlib",
+        NsisCompression::Bzip2 => "bzip2",
+        NsisCompression::Lzma => "lzma",
+      }),
+    );
+
     data.insert(
       "display_language_selector",
       to_json(nsis.display_language_selector && languages.len() > 1),
@@ -364,9 +371,9 @@ fn build_nsis_app_installer(
     }
     WebviewInstallMode::OfflineInstaller { silent: _ } => {
       let guid = if arch == "x64" {
-        WEBVIEW2_X64_INSTALLER_GUID
+        WEBVIEW2_X64_OFFLINE_INSTALLER_GUID
       } else {
-        WEBVIEW2_X86_INSTALLER_GUID
+        WEBVIEW2_X86_OFFLINE_INSTALLER_GUID
       };
       let offline_installer_path = tauri_tools_path
         .join("Webview2OfflineInstaller")
@@ -522,16 +529,18 @@ fn association_description(
 }
 
 /// BTreeMap<OriginalPath, (ParentOfTargetPath, TargetPath)>
-type ResourcesMap = BTreeMap<PathBuf, (String, PathBuf)>;
+type ResourcesMap = BTreeMap<PathBuf, (PathBuf, PathBuf)>;
 fn generate_resource_data(settings: &Settings) -> crate::Result<ResourcesMap> {
   let mut resources = ResourcesMap::new();
   let cwd = std::env::current_dir()?;
 
   let mut added_resources = Vec::new();
 
-  for src in settings.resource_files() {
-    let src = src?;
-    let resource_path = dunce::canonicalize(cwd.join(&src))?;
+  for resource in settings.resource_files().iter() {
+    let resource = resource?;
+
+    let src = cwd.join(resource.path());
+    let resource_path = dunce::simplified(&src).to_path_buf();
 
     // In some glob resource paths like `assets/**/*` a file might appear twice
     // because the `tauri_utils::resources::ResourcePaths` iterator also reads a directory
@@ -541,15 +550,15 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourcesMap> {
     }
     added_resources.push(resource_path.clone());
 
-    let target_path = resource_relpath(&src);
+    let target_path = resource.target();
     resources.insert(
       resource_path,
       (
         target_path
           .parent()
-          .map(|p| p.to_string_lossy().to_string())
-          .unwrap_or_default(),
-        target_path,
+          .expect("Couldn't get parent of target path")
+          .to_path_buf(),
+        target_path.to_path_buf(),
       ),
     );
   }
@@ -605,6 +614,7 @@ fn get_lang_data(
     "bulgarian" => Some(include_str!("./templates/nsis-languages/Bulgarian.nsh")),
     "dutch" => Some(include_str!("./templates/nsis-languages/Dutch.nsh")),
     "english" => Some(include_str!("./templates/nsis-languages/English.nsh")),
+    "german" => Some(include_str!("./templates/nsis-languages/German.nsh")),
     "japanese" => Some(include_str!("./templates/nsis-languages/Japanese.nsh")),
     "korean" => Some(include_str!("./templates/nsis-languages/Korean.nsh")),
     "portuguesebr" => Some(include_str!("./templates/nsis-languages/PortugueseBR.nsh")),

+ 67 - 15
tooling/bundler/src/bundle/windows/sign.rs

@@ -3,8 +3,10 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use crate::bundle::common::CommandExt;
-use bitness::{self, Bitness};
+use crate::{
+  bundle::{common::CommandExt, windows::util},
+  Settings,
+};
 use log::{debug, info};
 use std::{
   path::{Path, PathBuf},
@@ -69,11 +71,7 @@ fn locate_signtool() -> crate::Result<PathBuf> {
   kit_bin_paths.push(kits_root_10_bin_path);
 
   // Choose which version of SignTool to use based on OS bitness
-  let arch_dir = match bitness::os_bitness().expect("failed to get os bitness") {
-    Bitness::X86_32 => "x86",
-    Bitness::X86_64 => "x64",
-    _ => return Err(crate::Error::UnsupportedBitness),
-  };
+  let arch_dir = util::os_bitness().ok_or(crate::Error::UnsupportedBitness)?;
 
   /* Iterate through all bin paths, checking for existence of a SignTool executable. */
   for kit_bin_path in &kit_bin_paths {
@@ -90,18 +88,25 @@ fn locate_signtool() -> crate::Result<PathBuf> {
   Err(crate::Error::SignToolNotFound)
 }
 
-pub fn sign<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<()> {
-  // Convert path to string reference, as we need to pass it as a command-line parameter to signtool
-  let path_str = path.as_ref().to_str().unwrap();
+/// Check if binary is already signed.
+/// Used to skip sidecar binaries that are already signed.
+pub fn verify(path: &Path) -> crate::Result<bool> {
+  // Construct SignTool command
+  let signtool = locate_signtool()?;
 
-  info!(action = "Signing"; "{} with identity \"{}\"", path_str, params.certificate_thumbprint);
+  let mut cmd = Command::new(&signtool);
+  cmd.arg("verify");
+  cmd.arg("/pa");
+  cmd.arg(path);
+
+  Ok(cmd.status()?.success())
+}
 
+pub fn sign_command(path: &str, params: &SignParams) -> crate::Result<(Command, PathBuf)> {
   // Construct SignTool command
   let signtool = locate_signtool()?;
 
-  debug!("Running signtool {:?}", signtool);
-
-  let mut cmd = Command::new(signtool);
+  let mut cmd = Command::new(&signtool);
   cmd.arg("sign");
   cmd.args(["/fd", &params.digest_algorithm]);
   cmd.args(["/sha1", &params.certificate_thumbprint]);
@@ -116,7 +121,18 @@ pub fn sign<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<()> {
     }
   }
 
-  cmd.arg(path_str);
+  cmd.arg(path);
+
+  Ok((cmd, signtool))
+}
+
+pub fn sign<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<()> {
+  let path_str = path.as_ref().to_str().unwrap();
+
+  info!(action = "Signing"; "{} with identity \"{}\"", path_str, params.certificate_thumbprint);
+
+  let (mut cmd, signtool) = sign_command(path_str, params)?;
+  debug!("Running signtool {:?}", signtool);
 
   // Execute SignTool command
   let output = cmd.output_ok()?;
@@ -126,3 +142,39 @@ pub fn sign<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<()> {
 
   Ok(())
 }
+
+impl Settings {
+  pub(crate) fn can_sign(&self) -> bool {
+    self.windows().certificate_thumbprint.is_some()
+  }
+  pub(crate) fn sign_params(&self) -> SignParams {
+    SignParams {
+      product_name: self.product_name().into(),
+      digest_algorithm: self
+        .windows()
+        .digest_algorithm
+        .as_ref()
+        .map(|algorithm| algorithm.to_string())
+        .unwrap_or_else(|| "sha256".to_string()),
+      certificate_thumbprint: self
+        .windows()
+        .certificate_thumbprint
+        .clone()
+        .unwrap_or_default(),
+      timestamp_url: self
+        .windows()
+        .timestamp_url
+        .as_ref()
+        .map(|url| url.to_string()),
+      tsp: self.windows().tsp,
+    }
+  }
+}
+
+pub fn try_sign(file_path: &std::path::PathBuf, settings: &Settings) -> crate::Result<()> {
+  if settings.can_sign() {
+    info!(action = "Signing"; "{}", tauri_utils::display_path(file_path));
+    sign(file_path, &settings.sign_params())?;
+  }
+  Ok(())
+}

+ 54 - 39
tooling/bundler/src/bundle/windows/templates/installer.nsi

@@ -1,8 +1,19 @@
+Unicode true
+; Set the compression algorithm. Default is LZMA.
+!if "{{compression}}" == ""
+  SetCompressor /SOLID lzma
+!else
+  SetCompressor /SOLID "{{compression}}"
+!endif
+
 !include MUI2.nsh
 !include FileFunc.nsh
 !include x64.nsh
 !include WordFunc.nsh
 !include "FileAssociation.nsh"
+!include "StrFunc.nsh"
+${StrCase}
+${StrLoc}
 
 !define MANUFACTURER "{{manufacturer}}"
 !define PRODUCTNAME "{{product_name}}"
@@ -17,6 +28,7 @@
 !define MAINBINARYNAME "{{main_binary_name}}"
 !define MAINBINARYSRCPATH "{{main_binary_path}}"
 !define BUNDLEID "{{bundle_id}}"
+!define COPYRIGHT "{{copyright}}"
 !define OUTFILE "{{out_file}}"
 !define ARCH "{{arch}}"
 !define PLUGINSPATH "{{additional_plugins_path}}"
@@ -28,12 +40,11 @@
 !define WEBVIEW2INSTALLERPATH "{{webview2_installer_path}}"
 !define UNINSTKEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCTNAME}"
 !define MANUPRODUCTKEY "Software\${MANUFACTURER}\${PRODUCTNAME}"
+!define UNINSTALLERSIGNCOMMAND "{{uninstaller_sign_cmd}}"
 
 Name "${PRODUCTNAME}"
-BrandingText "{{copyright}}"
+BrandingText "${COPYRIGHT}"
 OutFile "${OUTFILE}"
-Unicode true
-SetCompressor /SOLID lzma
 
 VIProductVersion "${VERSIONWITHBUILD}"
 VIAddVersionKey "ProductName" "${PRODUCTNAME}"
@@ -47,6 +58,10 @@ VIAddVersionKey "ProductVersion" "${VERSION}"
     !addplugindir "${PLUGINSPATH}"
 !endif
 
+!if "${UNINSTALLERSIGNCOMMAND}" != ""
+  !uninstfinalize '${UNINSTALLERSIGNCOMMAND}'
+!endif
+
 ; Handle install mode, `perUser`, `perMachine` or `both`
 !if "${INSTALLMODE}" == "perMachine"
   RequestExecutionLevel highest
@@ -136,7 +151,11 @@ Function PageReinstall
     ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "DisplayName"
     ReadRegStr $R1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "Publisher"
     StrCmp "$R0$R1" "${PRODUCTNAME}${MANUFACTURER}" 0 wix_loop
-    StrCpy $R5 "wix"
+    ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "UninstallString"
+    ${StrCase} $R1 $R0 "L"
+    ${StrLoc} $R0 $R1 "msiexec" ">"
+    StrCmp $R0 0 0 wix_done
+    StrCpy $R7 "wix"
     StrCpy $R6 "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1"
     Goto compare_version
   wix_done:
@@ -150,7 +169,7 @@ Function PageReinstall
   ; and modify the messages presented to the user accordingly
   compare_version:
   StrCpy $R4 "$(older)"
-  ${If} $R5 == "wix"
+  ${If} $R7 == "wix"
     ReadRegStr $R0 HKLM "$R6" "DisplayVersion"
   ${Else}
     ReadRegStr $R0 SHCTX "${UNINSTKEY}" "DisplayVersion"
@@ -244,15 +263,14 @@ Function PageLeaveReinstall
   reinst_uninstall:
     HideWindow
     ClearErrors
-    ExecWait '$R1 /P _?=$4' $0
 
-    ${If} $R5 == "wix"
+    ${If} $R7 == "wix"
       ReadRegStr $R1 HKLM "$R6" "UninstallString"
       ExecWait '$R1' $0
     ${Else}
       ReadRegStr $4 SHCTX "${MANUPRODUCTKEY}" ""
       ReadRegStr $R1 SHCTX "${UNINSTKEY}" "UninstallString"
-      ExecWait '$R1 _?=$4' $0
+      ExecWait '$R1 /P _?=$4' $0
     ${EndIf}
 
     BringToFront
@@ -338,16 +356,7 @@ FunctionEnd
   !include "{{this}}"
 {{/each}}
 
-Var PassiveMode
-Function .onInit
-  ${GetOptions} $CMDLINE "/P" $PassiveMode
-  IfErrors +2 0
-    StrCpy $PassiveMode 1
-
-  !if "${DISPLAYLANGUAGESELECTOR}" == "true"
-    !insertmacro MUI_LANGDLL_DISPLAY
-  !endif
-
+!macro SetContext
   !if "${INSTALLMODE}" == "currentUser"
     SetShellVarContext current
   !else if "${INSTALLMODE}" == "perMachine"
@@ -363,6 +372,19 @@ Function .onInit
       SetRegView 32
     !endif
   ${EndIf}
+!macroend
+
+Var PassiveMode
+Function .onInit
+  ${GetOptions} $CMDLINE "/P" $PassiveMode
+  IfErrors +2 0
+    StrCpy $PassiveMode 1
+
+  !if "${DISPLAYLANGUAGESELECTOR}" == "true"
+    !insertmacro MUI_LANGDLL_DISPLAY
+  !endif
+
+  !insertmacro SetContext
 
   ${If} $INSTDIR == ""
     ; Set default install location
@@ -440,18 +462,18 @@ Section WebView2
   !endif
 
   !if "${INSTALLWEBVIEW2MODE}" == "embedBootstrapper"
-    CreateDirectory "$INSTDIR\redist"
-    File "/oname=$INSTDIR\redist\MicrosoftEdgeWebview2Setup.exe" "${WEBVIEW2BOOTSTRAPPERPATH}"
+    Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe"
+    File "/oname=$TEMP\MicrosoftEdgeWebview2Setup.exe" "${WEBVIEW2BOOTSTRAPPERPATH}"
     DetailPrint "$(installingWebview2)"
-    StrCpy $6 "$INSTDIR\redist\MicrosoftEdgeWebview2Setup.exe"
+    StrCpy $6 "$TEMP\MicrosoftEdgeWebview2Setup.exe"
     Goto install_webview2
   !endif
 
   !if "${INSTALLWEBVIEW2MODE}" == "offlineInstaller"
-    CreateDirectory "$INSTDIR\redist"
-    File "/oname=$INSTDIR\redist\MicrosoftEdgeWebView2RuntimeInstaller.exe" "${WEBVIEW2INSTALLERPATH}"
+    Delete "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe"
+    File "/oname=$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe" "${WEBVIEW2INSTALLERPATH}"
     DetailPrint "$(installingWebview2)"
-    StrCpy $6 "$INSTDIR\redist\MicrosoftEdgeWebView2RuntimeInstaller.exe"
+    StrCpy $6 "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe"
     Goto install_webview2
   !endif
 
@@ -596,15 +618,7 @@ Function .onInstSuccess
 FunctionEnd
 
 Function un.onInit
-  ${If} ${RunningX64}
-    !if "${ARCH}" == "x64"
-      SetRegView 64
-    !else if "${ARCH}" == "arm64"
-      SetRegView 64
-    !else
-      SetRegView 32
-    !endif
-  ${EndIf}
+  !insertmacro SetContext
 
   !if "${INSTALLMODE}" == "both"
     !insertmacro MULTIUSER_UNINIT
@@ -651,12 +665,6 @@ Section Uninstall
   ; Remove desktop shortcuts
   Delete "$DESKTOP\${MAINBINARYNAME}.lnk"
 
-  ; Delete app data
-  ${If} $DeleteAppDataCheckboxState == 1
-    RmDir /r "$APPDATA\${BUNDLEID}"
-    RmDir /r "$LOCALAPPDATA\${BUNDLEID}"
-  ${EndIf}
-
   ; Remove registry information for add/remove programs
   !if "${INSTALLMODE}" == "both"
     DeleteRegKey SHCTX "${UNINSTKEY}"
@@ -668,6 +676,13 @@ Section Uninstall
 
   DeleteRegValue HKCU "${MANUPRODUCTKEY}" "Installer Language"
 
+  ; Delete app data
+  ${If} $DeleteAppDataCheckboxState == 1
+    SetShellVarContext current
+    RmDir /r "$APPDATA\${BUNDLEID}"
+    RmDir /r "$LOCALAPPDATA\${BUNDLEID}"
+  ${EndIf}
+
   ${GetOptions} $CMDLINE "/P" $R0
   IfErrors +2 0
     SetAutoClose true

+ 27 - 0
tooling/bundler/src/bundle/windows/templates/nsis-languages/German.nsh

@@ -0,0 +1,27 @@
+LangString addOrReinstall ${LANG_GERMAN} "Komponenten hinzufügen/neu installieren"
+LangString alreadyInstalled ${LANG_GERMAN} "Bereits installiert"
+LangString alreadyInstalledLong ${LANG_GERMAN} "${PRODUCTNAME} ${VERSION} ist bereits installiert. Wählen Sie den gewünschten Vorgang aus und klicken Sie auf Weiter, um fortzufahren."
+LangString appRunning ${LANG_GERMAN} "${PRODUCTNAME} wird ausgeführt! Bitte schließen Sie es zuerst und versuchen Sie es dann erneut."
+LangString appRunningOkKill ${LANG_GERMAN} "${PRODUCTNAME} läuft! $\nKlicken Sie auf OK, um es zu beenden"
+LangString chooseMaintenanceOption ${LANG_GERMAN} "Wählen Sie die auszuführende Wartungsoption."
+LangString choowHowToInstall ${LANG_GERMAN} "Wählen Sie, wie Sie ${PRODUCTNAME} installieren möchten."
+LangString createDesktop ${LANG_GERMAN} "Desktop-Verknüpfung erstellen"
+LangString dontUninstall ${LANG_GERMAN} "Nicht deinstallieren"
+LangString dontUninstallDowngrade ${LANG_GERMAN} "Nicht deinstallieren (Downgrading ohne Deinstallation ist für dieses Installationsprogramm deaktiviert)"
+LangString failedToKillApp ${LANG_GERMAN} "Failed to kill ${PRODUCTNAME}. Bitte schließen Sie es zuerst und versuchen Sie es dann erneut"
+LangString installingWebview2 ${LANG_GERMAN} "Installiere WebView2..."
+LangString newerVersionInstalled ${LANG_GERMAN} "Eine neuere Version von ${PRODUCTNAME} ist bereits installiert! Es wird nicht empfohlen, eine ältere Version zu installieren. Wenn Sie diese ältere Version wirklich installieren wollen, ist es besser, die aktuelle Version zuerst zu deinstallieren. Wählen Sie den gewünschten Vorgang aus und klicken Sie auf Weiter, um fortzufahren."
+LangString älter ${LANG_GERMAN} "älter"
+LangString olderOrUnknownVersionInstalled ${LANG_GERMAN} "Eine $R4-Version von ${PRODUCTNAME} ist auf Ihrem System installiert. Es wird empfohlen, dass Sie die aktuelle Version vor der Installation deinstallieren. Wählen Sie den gewünschten Vorgang aus und klicken Sie auf Weiter, um fortzufahren."
+LangString silentDowngrades ${LANG_GERMAN} "Downgrades sind für dieses Installationsprogramm deaktiviert, Sie können nicht mit dem Silent-Installationsprogramm fortfahren, bitte verwenden Sie stattdessen das Installationsprogramm mit grafischer Benutzeroberfläche.$\n"
+LangString unableToUninstall ${LANG_GERMAN} "Unable to uninstall!"
+LangString uninstallApp ${LANG_GERMAN} "Deinstalliere ${PRODUCTNAME}"
+LangString uninstallBeforeInstalling ${LANG_GERMAN} "Vor der Installation deinstallieren"
+LangString unbekannt ${LANG_GERMAN} "unbekannt"
+LangString webview2AbortError ${LANG_GERMAN} "Die Installation von WebView2 ist fehlgeschlagen! Die Anwendung kann ohne es nicht laufen. Versuchen Sie, das Installationsprogramm neu zu starten."
+LangString webview2DownloadError ${LANG_GERMAN} "Fehler: Herunterladen von WebView2 fehlgeschlagen - $0"
+LangString webview2DownloadSuccess ${LANG_GERMAN} "WebView2 Bootstrapper erfolgreich heruntergeladen"
+LangString webview2Downloading ${LANG_GERMAN} "Herunterladen des WebView2 Bootstrappers..."
+LangString webview2InstallError ${LANG_GERMAN} "Fehler: Die Installation von WebView2 ist mit Exit Code $1 fehlgeschlagen"
+LangString webview2InstallSuccess ${LANG_GERMAN} "WebView2 erfolgreich installiert"
+LangString deleteAppData ${LANG_GERMAN} "Lösche die Anwendungsdaten"

+ 21 - 37
tooling/bundler/src/bundle/windows/util.rs

@@ -12,14 +12,9 @@ use log::info;
 use sha2::Digest;
 use zip::ZipArchive;
 
-#[cfg(target_os = "windows")]
-use crate::bundle::windows::sign::{sign, SignParams};
-#[cfg(target_os = "windows")]
-use crate::Settings;
-
 pub const WEBVIEW2_BOOTSTRAPPER_URL: &str = "https://go.microsoft.com/fwlink/p/?LinkId=2124703";
-pub const WEBVIEW2_X86_INSTALLER_GUID: &str = "a17bde80-b5ab-47b5-8bbb-1cbe93fc6ec9";
-pub const WEBVIEW2_X64_INSTALLER_GUID: &str = "aa5fd9b3-dc11-4cbc-8343-a50f57b311e1";
+pub const WEBVIEW2_X86_OFFLINE_INSTALLER_GUID: &str = "2c122012-898d-4a69-9ab6-aa50bbe81031";
+pub const WEBVIEW2_X64_OFFLINE_INSTALLER_GUID: &str = "0af26c79-02f0-4f06-a12d-116bc05ca860";
 pub const NSIS_OUTPUT_FOLDER_NAME: &str = "nsis";
 pub const NSIS_UPDATER_OUTPUT_FOLDER_NAME: &str = "nsis-updater";
 pub const WIX_OUTPUT_FOLDER_NAME: &str = "msi";
@@ -27,7 +22,9 @@ pub const WIX_UPDATER_OUTPUT_FOLDER_NAME: &str = "msi-updater";
 
 pub fn download(url: &str) -> crate::Result<Vec<u8>> {
   info!(action = "Downloading"; "{}", url);
-  let response = ureq::get(url).call().map_err(Box::new)?;
+
+  let agent = ureq::AgentBuilder::new().try_proxy_from_env(true).build();
+  let response = agent.get(url).call().map_err(Box::new)?;
   let mut bytes = Vec::new();
   response.into_reader().read_to_end(&mut bytes)?;
   Ok(bytes)
@@ -75,35 +72,6 @@ fn verify(data: &Vec<u8>, hash: &str, mut hasher: impl Digest) -> crate::Result<
   }
 }
 
-#[cfg(target_os = "windows")]
-pub fn try_sign(file_path: &std::path::PathBuf, settings: &Settings) -> crate::Result<()> {
-  use tauri_utils::display_path;
-
-  if let Some(certificate_thumbprint) = settings.windows().certificate_thumbprint.as_ref() {
-    info!(action = "Signing"; "{}", display_path(file_path));
-    sign(
-      file_path,
-      &SignParams {
-        product_name: settings.product_name().into(),
-        digest_algorithm: settings
-          .windows()
-          .digest_algorithm
-          .as_ref()
-          .map(|algorithm| algorithm.to_string())
-          .unwrap_or_else(|| "sha256".to_string()),
-        certificate_thumbprint: certificate_thumbprint.to_string(),
-        timestamp_url: settings
-          .windows()
-          .timestamp_url
-          .as_ref()
-          .map(|url| url.to_string()),
-        tsp: settings.windows().tsp,
-      },
-    )?;
-  }
-  Ok(())
-}
-
 /// Extracts the zips from memory into a useable path.
 pub fn extract_zip(data: &[u8], path: &Path) -> crate::Result<()> {
   let cursor = Cursor::new(data);
@@ -136,3 +104,19 @@ pub fn extract_zip(data: &[u8], path: &Path) -> crate::Result<()> {
 
   Ok(())
 }
+
+#[cfg(target_os = "windows")]
+pub fn os_bitness<'a>() -> Option<&'a str> {
+  use windows_sys::Win32::System::{
+    Diagnostics::Debug::{PROCESSOR_ARCHITECTURE_AMD64, PROCESSOR_ARCHITECTURE_INTEL},
+    SystemInformation::{GetNativeSystemInfo, SYSTEM_INFO},
+  };
+
+  let mut system_info: SYSTEM_INFO = unsafe { std::mem::zeroed() };
+  unsafe { GetNativeSystemInfo(&mut system_info) };
+  match unsafe { system_info.Anonymous.Anonymous.wProcessorArchitecture } {
+    PROCESSOR_ARCHITECTURE_INTEL => Some("x86"),
+    PROCESSOR_ARCHITECTURE_AMD64 => Some("x64"),
+    _ => None,
+  }
+}

+ 57 - 0
tooling/cli/CHANGELOG.md

@@ -250,6 +250,63 @@
 - First mobile alpha release!
   - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08
 
+## \[1.5.5]
+
+### Enhancements
+
+- [`9bead42d`](https://www.github.com/tauri-apps/tauri/commit/9bead42dbca0fb6dd7ea0b6bfb2f2308a5c5f992)([#8059](https://www.github.com/tauri-apps/tauri/pull/8059)) Allow rotating the updater private key.
+
+### Bug Fixes
+
+- [`be8e5aa3`](https://www.github.com/tauri-apps/tauri/commit/be8e5aa3071d9bc5d0bd24647e8168f312d11c8d)([#8042](https://www.github.com/tauri-apps/tauri/pull/8042)) Fixes duplicated newlines on command outputs.
+
+### Dependencies
+
+- Upgraded to `tauri-bundler@1.4.4`
+
+## \[1.5.4]
+
+### Dependencies
+
+- Upgraded to `tauri-bundler@1.4.3`
+
+## \[1.5.3]
+
+### Dependencies
+
+- Upgraded to `tauri-bundler@1.4.2`
+
+## \[1.5.2]
+
+### Dependencies
+
+- Upgraded to `tauri-bundler@1.4.1`
+
+## \[1.5.1]
+
+### Bug Fixes
+
+- [`d6eb46cf`](https://www.github.com/tauri-apps/tauri/commit/d6eb46cf1116d147121f6b6db9d390b5e2fb238d)([#7934](https://www.github.com/tauri-apps/tauri/pull/7934)) On macOS, fix the `apple-id` option name when using `notarytools submit`.
+
+## \[1.5.0]
+
+### New Features
+
+- [`e1526626`](https://www.github.com/tauri-apps/tauri/commit/e152662687ece7a62d383923a50751cc0dd34331)([#7723](https://www.github.com/tauri-apps/tauri/pull/7723)) Support Bun package manager in CLI
+
+### Enhancements
+
+- [`13279917`](https://www.github.com/tauri-apps/tauri/commit/13279917d4cae071d0ce3a686184d48af079f58a)([#7713](https://www.github.com/tauri-apps/tauri/pull/7713)) Add version of Rust Tauri CLI installed with Cargo to `tauri info` command.
+
+### Bug Fixes
+
+- [`dad4f54e`](https://www.github.com/tauri-apps/tauri/commit/dad4f54eec9773d2ea6233a7d9fd218741173823)([#7277](https://www.github.com/tauri-apps/tauri/pull/7277)) Removed the automatic version check of the CLI that ran after `tauri` commands which caused various issues.
+
+### Dependencies
+
+- Upgraded to `tauri-bundler@1.4.0`
+- Upgraded to `tauri-utils@1.5.0`
+
 ## \[1.4.0]
 
 ### New Features

+ 163 - 154
tooling/cli/Cargo.lock

@@ -73,9 +73,9 @@ dependencies = [
 
 [[package]]
 name = "aho-corasick"
-version = "1.1.1"
+version = "1.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
 dependencies = [
  "memchr",
 ]
@@ -166,13 +166,13 @@ dependencies = [
 
 [[package]]
 name = "async-trait"
-version = "0.1.73"
+version = "0.1.74"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
+checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -298,9 +298,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitflags"
-version = "2.4.0"
+version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
 
 [[package]]
 name = "bitness"
@@ -340,9 +340,9 @@ dependencies = [
 
 [[package]]
 name = "bstr"
-version = "1.6.2"
+version = "1.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a"
+checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019"
 dependencies = [
  "memchr",
  "serde",
@@ -368,9 +368,9 @@ checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
 
 [[package]]
 name = "byteorder"
-version = "1.4.3"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
 [[package]]
 name = "bytes"
@@ -508,7 +508,7 @@ dependencies = [
  "heck",
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -761,7 +761,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
 dependencies = [
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -781,7 +781,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583"
 dependencies = [
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -824,7 +824,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "strsim",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -835,7 +835,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
 dependencies = [
  "darling_core",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -852,10 +852,11 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
 
 [[package]]
 name = "deranged"
-version = "0.3.8"
+version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
+checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
 dependencies = [
+ "powerfmt",
  "serde",
 ]
 
@@ -874,19 +875,20 @@ dependencies = [
 
 [[package]]
 name = "deunicode"
-version = "1.4.0"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "890d779e1bc371e4fa7727ef6d29a9346be20ddfe40cd8c744cd083ce0640b15"
+checksum = "6a1abaf4d861455be59f64fd2b55606cb151fce304ede7165f410243ce96bde6"
 
 [[package]]
 name = "dialoguer"
-version = "0.10.4"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87"
+checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de"
 dependencies = [
  "console",
  "shell-words",
  "tempfile",
+ "thiserror",
  "zeroize",
 ]
 
@@ -1104,25 +1106,14 @@ dependencies = [
 
 [[package]]
 name = "errno"
-version = "0.3.4"
+version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480"
+checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
 dependencies = [
- "errno-dragonfly",
  "libc",
  "windows-sys 0.48.0",
 ]
 
-[[package]]
-name = "errno-dragonfly"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
-dependencies = [
- "cc",
- "libc",
-]
-
 [[package]]
 name = "event-listener"
 version = "2.5.3"
@@ -1184,9 +1175,9 @@ dependencies = [
 
 [[package]]
 name = "flate2"
-version = "1.0.27"
+version = "1.0.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
+checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
 dependencies = [
  "crc32fast",
  "miniz_oxide",
@@ -1314,7 +1305,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -1500,9 +1491,9 @@ dependencies = [
 
 [[package]]
 name = "hashbrown"
-version = "0.14.1"
+version = "0.14.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12"
+checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
 
 [[package]]
 name = "heck"
@@ -1623,16 +1614,16 @@ dependencies = [
 
 [[package]]
 name = "iana-time-zone"
-version = "0.1.57"
+version = "0.1.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
+checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
 dependencies = [
  "android_system_properties",
  "core-foundation-sys 0.8.4",
  "iana-time-zone-haiku",
  "js-sys",
  "wasm-bindgen",
- "windows 0.48.0",
+ "windows-core",
 ]
 
 [[package]]
@@ -1733,7 +1724,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897"
 dependencies = [
  "equivalent",
- "hashbrown 0.14.1",
+ "hashbrown 0.14.2",
  "serde",
 ]
 
@@ -1862,9 +1853,9 @@ dependencies = [
 
 [[package]]
 name = "json-patch"
-version = "1.1.0"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f7765dccf8c39c3a470fc694efe322969d791e713ca46bc7b5c506886157572"
+checksum = "55ff1e1486799e3f64129f8ccad108b38290df9cd7015cd31bed17239f0789d6"
 dependencies = [
  "serde",
  "serde_json",
@@ -2129,9 +2120,9 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.148"
+version = "0.2.149"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
+checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
 
 [[package]]
 name = "libflate"
@@ -2178,9 +2169,9 @@ dependencies = [
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.4.8"
+version = "0.4.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db"
+checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
 
 [[package]]
 name = "local-ip-address"
@@ -2196,9 +2187,9 @@ dependencies = [
 
 [[package]]
 name = "lock_api"
-version = "0.4.10"
+version = "0.4.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
 dependencies = [
  "autocfg",
  "scopeguard",
@@ -2326,7 +2317,7 @@ version = "2.13.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fd063c93b900149304e3ba96ce5bf210cd4f81ef5eb80ded0d100df3e85a3ac0"
 dependencies = [
- "bitflags 2.4.0",
+ "bitflags 2.4.1",
  "ctor 0.2.5",
  "napi-derive",
  "napi-sys",
@@ -2432,7 +2423,7 @@ version = "0.27.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
 dependencies = [
- "bitflags 2.4.0",
+ "bitflags 2.4.1",
  "cfg-if",
  "libc",
 ]
@@ -2459,7 +2450,7 @@ version = "6.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
 dependencies = [
- "bitflags 2.4.0",
+ "bitflags 2.4.1",
  "crossbeam-channel",
  "filetime",
  "fsevent-sys",
@@ -2474,11 +2465,12 @@ dependencies = [
 
 [[package]]
 name = "notify-debouncer-mini"
-version = "0.3.0"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e55ee272914f4563a2f8b8553eb6811f3c0caea81c756346bad15b7e3ef969f0"
+checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43"
 dependencies = [
  "crossbeam-channel",
+ "log",
  "notify",
 ]
 
@@ -2557,9 +2549,9 @@ dependencies = [
 
 [[package]]
 name = "num-traits"
-version = "0.2.16"
+version = "0.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
+checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
 dependencies = [
  "autocfg",
 ]
@@ -2629,7 +2621,7 @@ version = "0.10.57"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
 dependencies = [
- "bitflags 2.4.0",
+ "bitflags 2.4.1",
  "cfg-if",
  "foreign-types",
  "libc",
@@ -2646,7 +2638,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -2679,9 +2671,9 @@ dependencies = [
 
 [[package]]
 name = "ordered-float"
-version = "2.10.0"
+version = "2.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87"
+checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
 dependencies = [
  "num-traits",
 ]
@@ -2719,13 +2711,13 @@ dependencies = [
 
 [[package]]
 name = "parking_lot_core"
-version = "0.9.8"
+version = "0.9.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
 dependencies = [
  "cfg-if",
  "libc",
- "redox_syscall 0.3.5",
+ "redox_syscall 0.4.1",
  "smallvec",
  "windows-targets 0.48.5",
 ]
@@ -2789,7 +2781,7 @@ dependencies = [
  "pest_meta",
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -2923,7 +2915,7 @@ dependencies = [
  "phf_shared 0.11.2",
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -2970,7 +2962,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -3030,6 +3022,12 @@ dependencies = [
  "universal-hash",
 ]
 
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
 [[package]]
 name = "ppv-lite86"
 version = "0.2.17"
@@ -3050,9 +3048,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.67"
+version = "1.0.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
 dependencies = [
  "unicode-ident",
 ]
@@ -3203,6 +3201,15 @@ dependencies = [
  "bitflags 1.3.2",
 ]
 
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
 [[package]]
 name = "redox_users"
 version = "0.4.3"
@@ -3216,9 +3223,9 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.9.6"
+version = "1.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff"
+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -3228,9 +3235,9 @@ dependencies = [
 
 [[package]]
 name = "regex-automata"
-version = "0.3.9"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -3239,9 +3246,9 @@ dependencies = [
 
 [[package]]
 name = "regex-syntax"
-version = "0.7.5"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
 [[package]]
 name = "reqwest"
@@ -3349,11 +3356,11 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.38.15"
+version = "0.38.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2f9da0cbd88f9f09e7814e388301c8414c51c62aa6ce1e4b5c551d49d96e531"
+checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0"
 dependencies = [
- "bitflags 2.4.0",
+ "bitflags 2.4.1",
  "errno",
  "libc",
  "linux-raw-sys",
@@ -3524,15 +3531,15 @@ dependencies = [
 
 [[package]]
 name = "semver"
-version = "1.0.19"
+version = "1.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
+checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
 
 [[package]]
 name = "serde"
-version = "1.0.188"
+version = "1.0.189"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
+checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
 dependencies = [
  "serde_derive",
 ]
@@ -3549,13 +3556,13 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.188"
+version = "1.0.189"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
+checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -3622,9 +3629,9 @@ dependencies = [
 
 [[package]]
 name = "serde_with"
-version = "3.3.0"
+version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237"
+checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23"
 dependencies = [
  "base64 0.21.4",
  "chrono",
@@ -3639,14 +3646,14 @@ dependencies = [
 
 [[package]]
 name = "serde_with_macros"
-version = "3.3.0"
+version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c"
+checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788"
 dependencies = [
  "darling",
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -3890,15 +3897,15 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
 
 [[package]]
 name = "sval"
-version = "2.9.2"
+version = "2.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f9150edabce0ada1e9b44f98d52817ba0fba9d572898da47e354a14a3eb406d"
+checksum = "b15df12a8db7c216a04b4b438f90d50d5335cd38f161b56389c9f5c9d96d0873"
 
 [[package]]
 name = "sval_buffer"
-version = "2.9.2"
+version = "2.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fb08e361c8fbbc37fb3d08dc067a98207062d083ee5ef0b21e3739b16e69892"
+checksum = "57e80556bc8acea0446e574ce542ad6114a76a0237f28a842bc01ca3ea98f479"
 dependencies = [
  "sval",
  "sval_ref",
@@ -3906,18 +3913,18 @@ dependencies = [
 
 [[package]]
 name = "sval_dynamic"
-version = "2.9.2"
+version = "2.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae417f3812ea4403cd0cc0819628427ef6e099d5f482d80ed4e0f92836c51a85"
+checksum = "9d93d2259edb1d7b4316179f0a98c62e3ffc726f47ab200e07cfe382771f57b8"
 dependencies = [
  "sval",
 ]
 
 [[package]]
 name = "sval_fmt"
-version = "2.9.2"
+version = "2.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97a898ac59b0f7a0344d0ac0f408908f545d422ffbfe46522a5cdff3ed391650"
+checksum = "532f7f882226f7a5a4656f5151224aaebf8217e0d539cb1595b831bace921343"
 dependencies = [
  "itoa 1.0.9",
  "ryu",
@@ -3926,9 +3933,9 @@ dependencies = [
 
 [[package]]
 name = "sval_json"
-version = "2.9.2"
+version = "2.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c63eda4f68a4df3d58f0c9805983560c1de8bf414800a990be25e433d1cccc8c"
+checksum = "76e03bd8aa0ae6ee018f7ae95c9714577687a4415bd1a5f19b26e34695f7e072"
 dependencies = [
  "itoa 1.0.9",
  "ryu",
@@ -3937,18 +3944,18 @@ dependencies = [
 
 [[package]]
 name = "sval_ref"
-version = "2.9.2"
+version = "2.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87e59d69dac5af4c6b87c79b52581a5b9ab9cc2d019775dea318967ea3c3effd"
+checksum = "75ed054f2fb8c2a0ab5d36c1ec57b412919700099fc5e32ad8e7a38b23e1a9e1"
 dependencies = [
  "sval",
 ]
 
 [[package]]
 name = "sval_serde"
-version = "2.9.2"
+version = "2.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93d72e44618c14d0f8aff885af8184a3579ffa0d9ad5b1fbffc59f85d6739982"
+checksum = "7ff191c4ff05b67e3844c161021427646cde5d6624597958be158357d9200586"
 dependencies = [
  "serde",
  "sval",
@@ -3969,9 +3976,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.37"
+version = "2.0.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
+checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -4065,7 +4072,8 @@ dependencies = [
  "ureq",
  "uuid",
  "walkdir",
- "winreg 0.50.0",
+ "windows-sys 0.48.0",
+ "winreg 0.51.0",
  "zip",
 ]
 
@@ -4124,7 +4132,7 @@ dependencies = [
  "textwrap",
  "thiserror",
  "tokio",
- "toml 0.7.8",
+ "toml 0.8.2",
  "toml_edit",
  "unicode-width",
  "ureq",
@@ -4210,7 +4218,7 @@ dependencies = [
  "serde_with",
  "serialize-to-javascript",
  "thiserror",
- "toml 0.7.8",
+ "toml 0.8.2",
  "url",
  "walkdir",
  "windows 0.51.1",
@@ -4277,22 +4285,22 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
 
 [[package]]
 name = "thiserror"
-version = "1.0.49"
+version = "1.0.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
+checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.49"
+version = "1.0.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
+checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -4318,12 +4326,13 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe"
+checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
 dependencies = [
  "deranged",
  "itoa 1.0.9",
+ "powerfmt",
  "serde",
  "time-core",
  "time-macros",
@@ -4361,9 +4370,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "tokio"
-version = "1.32.0"
+version = "1.33.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
+checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653"
 dependencies = [
  "backtrace",
  "bytes",
@@ -4384,7 +4393,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -4437,9 +4446,9 @@ dependencies = [
 
 [[package]]
 name = "toml"
-version = "0.7.8"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
+checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
 dependencies = [
  "serde",
  "serde_spanned",
@@ -4458,9 +4467,9 @@ dependencies = [
 
 [[package]]
 name = "toml_edit"
-version = "0.19.15"
+version = "0.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
 dependencies = [
  "indexmap 2.0.2",
  "serde",
@@ -4499,11 +4508,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
 
 [[package]]
 name = "tracing"
-version = "0.1.37"
+version = "0.1.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
 dependencies = [
- "cfg-if",
  "log",
  "pin-project-lite",
  "tracing-attributes",
@@ -4512,20 +4520,20 @@ dependencies = [
 
 [[package]]
 name = "tracing-attributes"
-version = "0.1.26"
+version = "0.1.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
 name = "tracing-core"
-version = "0.1.31"
+version = "0.1.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
 dependencies = [
  "once_cell",
 ]
@@ -4677,9 +4685,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
 
 [[package]]
 name = "uuid"
-version = "1.4.1"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
+checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
 dependencies = [
  "getrandom 0.2.10",
  "sha1_smol",
@@ -4687,9 +4695,9 @@ dependencies = [
 
 [[package]]
 name = "value-bag"
-version = "1.4.1"
+version = "1.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3"
+checksum = "4a72e1902dde2bd6441347de2b70b7f5d59bf157c6c62f0c44572607a1d55bbe"
 dependencies = [
  "value-bag-serde1",
  "value-bag-sval2",
@@ -4697,9 +4705,9 @@ dependencies = [
 
 [[package]]
 name = "value-bag-serde1"
-version = "1.4.1"
+version = "1.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0b9f3feef403a50d4d67e9741a6d8fc688bcbb4e4f31bd4aab72cc690284394"
+checksum = "07ba39dc791ecb35baad371a3fc04c6eab688c04937d2e0ac6c22b612c0357bf"
 dependencies = [
  "erased-serde",
  "serde",
@@ -4708,9 +4716,9 @@ dependencies = [
 
 [[package]]
 name = "value-bag-sval2"
-version = "1.4.1"
+version = "1.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30b24f4146b6f3361e91cbf527d1fb35e9376c3c0cef72ca5ec5af6d640fad7d"
+checksum = "c3e06c10810a57bbf45778d023d432a50a1daa7d185991ae06bcfb6c654d0945"
 dependencies = [
  "sval",
  "sval_buffer",
@@ -4805,7 +4813,7 @@ dependencies = [
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
  "wasm-bindgen-shared",
 ]
 
@@ -4839,7 +4847,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
@@ -4929,15 +4937,6 @@ dependencies = [
  "windows_x86_64_msvc 0.39.0",
 ]
 
-[[package]]
-name = "windows"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
-dependencies = [
- "windows-targets 0.48.5",
-]
-
 [[package]]
 name = "windows"
 version = "0.51.1"
@@ -4977,7 +4976,7 @@ checksum = "fb2b158efec5af20d8846836622f50a87e6556b9153a42772fa047f773c0e555"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -4988,7 +4987,7 @@ checksum = "0546e63e1ce64c04403d2311fa0e3ab5ae3a367bd524b4a38d8d8d18c70cfa76"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -5187,6 +5186,16 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "winreg"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "937f3df7948156640f46aacef17a70db0de5917bda9c92b0f751f3a955b588fc"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
+
 [[package]]
 name = "xattr"
 version = "1.0.1"

+ 11 - 11
tooling/cli/Cargo.toml

@@ -48,35 +48,35 @@ jsonrpsee-ws-client = { version = "0.20", default-features = false }
 thiserror = "1"
 sublime_fuzzy = "0.7"
 clap_complete = "4"
-clap = { version = "4.3", features = [ "derive", "env" ] }
+clap = { version = "4.4", features = [ "derive", "env" ] }
 anyhow = "1.0"
 tauri-bundler = { version = "2.0.0-alpha.10", default-features = false, path = "../bundler" }
 colored = "2.0"
 once_cell = "1"
 serde = { version = "1.0", features = [ "derive" ] }
 serde_json = "1.0"
-notify = "6.0"
-notify-debouncer-mini = "0.3"
+notify = "6.1"
+notify-debouncer-mini = "0.4"
 shared_child = "1.0"
 duct = "0.13"
-toml_edit = "0.19"
-json-patch = "1.0"
+toml_edit = "0.20"
+json-patch = "1.2"
 tauri-utils = { version = "2.0.0-alpha.9", path = "../../core/tauri-utils", features = [ "isolation", "schema", "config-json5", "config-toml" ] }
 tauri-utils-v1 = { version = "1", package = "tauri-utils", features = [ "isolation", "schema", "config-json5", "config-toml" ] }
-toml = "0.7"
+toml = "0.8"
 jsonschema = "0.17"
-handlebars = "4.3"
+handlebars = "4.4"
 include_dir = "0.7"
 minisign = "=0.7.5"
-base64 = "0.21.2"
-ureq = { version = "2.7", default-features = false, features = [ "gzip" ] }
+base64 = "0.21.4"
+ureq = { version = "2.8", default-features = false, features = [ "gzip" ] }
 os_info = "3"
 semver = "1.0"
-regex = "1.9.3"
+regex = "1.6.0"
 unicode-width = "0.1"
 zeroize = "1.6"
 heck = { version = "0.4", features = [ "unicode" ] }
-dialoguer = "0.10"
+dialoguer = "0.11"
 url = { version = "2.4", features = [ "serde" ] }
 os_pipe = "1"
 ignore = "0.4"

+ 3 - 2
tooling/cli/ENVIRONMENT_VARIABLES.md

@@ -21,13 +21,14 @@ These environment variables are inputs to the CLI which may have an equivalent C
 - `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` — The signing private key password, see `TAURI_SIGNING_PRIVATE_KEY`.
 - `APPLE_CERTIFICATE` — Base64 encoded of the `.p12` certificate for code signing. To get this value, run `openssl base64 -in MyCertificate.p12 -out MyCertificate-base64.txt`.
 - `APPLE_CERTIFICATE_PASSWORD` — The password you used to export the certificate.
-- `APPLE_ID` — The Apple ID used to notarize the application. If this environment variable is provided, `APPLE_PASSWORD` must also be set. Alternatively, `APPLE_API_KEY` and `APPLE_API_ISSUER` can be used to authenticate.
+- `APPLE_ID` — The Apple ID used to notarize the application. If this environment variable is provided, `APPLE_PASSWORD` and `APPLE_TEAM_ID` must also be set. Alternatively, `APPLE_API_KEY` and `APPLE_API_ISSUER` can be used to authenticate.
 - `APPLE_PASSWORD` — The Apple password used to authenticate for application notarization. Required if `APPLE_ID` is specified. An app-specific password can be used. Alternatively to entering the password in plaintext, it may also be specified using a '@keychain:' or '@env:' prefix followed by a keychain password item name or environment variable name.
+- `APPLE_TEAM_ID`: Developer team ID. To find your Team ID, go to the [Account](https://developer.apple.com/account) page on the Apple Developer website, and check your membership details.
 - `APPLE_API_KEY` — Alternative to `APPLE_ID` and `APPLE_PASSWORD` for notarization authentication using JWT.
-  - This option will search the following directories in sequence for a private key file with the name of `AuthKey\_<api_key>.p8`: `./private_keys`, `~/private_keys`, `~/.private_keys`, and `~/.appstoreconnect/private_keys`. Additionally, you can set environment variable `API_PRIVATE_KEYS_DIR` to specify the directory where your AuthKey file is located.
   - See [creating API keys](https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api) for more information.
 - `API_PRIVATE_KEYS_DIR` — Specify the directory where your AuthKey file is located. See `APPLE_API_KEY`.
 - `APPLE_API_ISSUER` — Issuer ID. Required if `APPLE_API_KEY` is specified.
+- `APPLE_API_KEY_PATH` - path to the API key `.p8` file. If not specified, the bundler searches the following directories in sequence for a private key file with the name of 'AuthKey_<api_key>.p8': './private_keys', '~/private_keys', '~/.private_keys', and '~/.appstoreconnect/private_keys'.
 - `APPLE_SIGNING_IDENTITY` — The identity used to code sign. Overwrites `tauri.conf.json > tauri > bundle > macOS > signingIdentity`.
 - `APPLE_PROVIDER_SHORT_NAME` — If your Apple ID is connected to multiple teams, you have to specify the provider short name of the team you want to use to notarize your app. Overwrites `tauri.conf.json > tauri > bundle > macOS > providerShortName`.
 - `APPLE_DEVELOPMENT_TEAM` — TODO

+ 3 - 3
tooling/cli/metadata.json

@@ -1,8 +1,8 @@
 {
   "cli.js": {
-    "version": "1.4.0",
+    "version": "1.5.5",
     "node": ">= 10.0.0"
   },
-  "tauri": "1.4.0",
-  "tauri-build": "1.4.0"
+  "tauri": "1.5.2",
+  "tauri-build": "1.5.0"
 }

+ 60 - 0
tooling/cli/node/CHANGELOG.md

@@ -240,6 +240,66 @@
 - First mobile alpha release!
   - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08
 
+## \[1.5.5]
+
+### Enhancements
+
+- [`9bead42d`](https://www.github.com/tauri-apps/tauri/commit/9bead42dbca0fb6dd7ea0b6bfb2f2308a5c5f992)([#8059](https://www.github.com/tauri-apps/tauri/pull/8059)) Allow rotating the updater private key.
+
+### Bug Fixes
+
+- [`be8e5aa3`](https://www.github.com/tauri-apps/tauri/commit/be8e5aa3071d9bc5d0bd24647e8168f312d11c8d)([#8042](https://www.github.com/tauri-apps/tauri/pull/8042)) Fixes duplicated newlines on command outputs.
+
+### Dependencies
+
+- Upgraded to `tauri-cli@1.5.5`
+
+## \[1.5.4]
+
+### Dependencies
+
+- Upgraded to `tauri-cli@1.5.4`
+
+## \[1.5.3]
+
+### Dependencies
+
+- Upgraded to `tauri-cli@1.5.3`
+
+## \[1.5.2]
+
+### Dependencies
+
+- Upgraded to `tauri-cli@1.5.2`
+
+## \[1.5.1]
+
+### Bug Fixes
+
+- [`d6eb46cf`](https://www.github.com/tauri-apps/tauri/commit/d6eb46cf1116d147121f6b6db9d390b5e2fb238d)([#7934](https://www.github.com/tauri-apps/tauri/pull/7934)) On macOS, fix the `apple-id` option name when using `notarytools submit`.
+
+### Dependencies
+
+- Upgraded to `tauri-cli@1.5.1`
+
+## \[1.5.0]
+
+### New Features
+
+- [`e1526626`](https://www.github.com/tauri-apps/tauri/commit/e152662687ece7a62d383923a50751cc0dd34331)([#7723](https://www.github.com/tauri-apps/tauri/pull/7723)) Support Bun package manager in CLI
+
+### Enhancements
+
+- [`13279917`](https://www.github.com/tauri-apps/tauri/commit/13279917d4cae071d0ce3a686184d48af079f58a)([#7713](https://www.github.com/tauri-apps/tauri/pull/7713)) Add version of Rust Tauri CLI installed with Cargo to `tauri info` command.
+
+### Bug Fixes
+
+- [`dad4f54e`](https://www.github.com/tauri-apps/tauri/commit/dad4f54eec9773d2ea6233a7d9fd218741173823)([#7277](https://www.github.com/tauri-apps/tauri/pull/7277)) Removed the automatic version check of the CLI that ran after `tauri` commands which caused various issues.
+
+### Dependencies
+
+- Upgraded to `tauri-cli@1.5.0`
+
 ## \[1.4.0]
 
 ### New Features

+ 1 - 1
tooling/cli/node/README.md

@@ -19,7 +19,7 @@ Tauri is a polyglot and generic system that is very composable and allows engine
 
 Tauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task.
 ## This module
-Written in Typescript and packaged such that it can be used with `npm`, `pnpm`, and `yarn`, this library provides a node.js runner for common tasks when using Tauri, like `yarn tauri dev`. For the most part it is a wrapper around [tauri-cli](https://github.com/tauri-apps/tauri/blob/dev/tooling/cli).
+Written in Typescript and packaged such that it can be used with `npm`, `pnpm`, `yarn`, and `bun`, this library provides a node.js runner for common tasks when using Tauri, like `yarn tauri dev`. For the most part it is a wrapper around [tauri-cli](https://github.com/tauri-apps/tauri/blob/dev/tooling/cli).
 
 To learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document.
 

+ 1 - 1
tooling/cli/node/package.json

@@ -42,7 +42,7 @@
     "cross-env": "7.0.3",
     "cross-spawn": "7.0.3",
     "fs-extra": "11.1.1",
-    "jest": "29.5.0",
+    "jest": "29.7.0",
     "jest-transform-toml": "1.0.0",
     "prettier": "2.8.8"
   },

+ 5 - 5
tooling/cli/node/tauri.js

@@ -7,7 +7,7 @@
 const cli = require('./main')
 const path = require('path')
 
-const [bin, script, ...arguments] = process.argv
+const [bin, script, ...args] = process.argv
 const binStem = path.parse(bin).name.toLowerCase()
 
 // We want to make a helpful binary name for the underlying CLI helper, if we
@@ -20,7 +20,7 @@ if (bin === '@tauri-apps/cli') {
 }
 // Even if started by a package manager, the binary will be NodeJS.
 // Some distribution still use "nodejs" as the binary name.
-else if (binStem.match(/(nodejs|node)\-?([0-9]*)*$/g)) {
+else if (binStem.match(/(nodejs|node|bun)\-?([0-9]*)*$/g)) {
   const managerStem = process.env.npm_execpath
     ? path.parse(process.env.npm_execpath).name.toLowerCase()
     : null
@@ -32,7 +32,7 @@ else if (binStem.match(/(nodejs|node)\-?([0-9]*)*$/g)) {
         manager = 'npm'
         break
 
-      // Yarn and pnpm have the same stem name as their bin.
+      // Yarn, pnpm, and bun have the same stem name as their bin.
       // We assume all unknown package managers do as well.
       default:
         manager = managerStem
@@ -48,10 +48,10 @@ else if (binStem.match(/(nodejs|node)\-?([0-9]*)*$/g)) {
   }
 } else {
   // We don't know what started it, assume it's already stripped.
-  arguments.unshift(bin)
+  args.unshift(bin)
 }
 
-cli.run(arguments, binName).catch((err) => {
+cli.run(args, binName).catch((err) => {
   cli.logError(err.message)
   process.exit(1)
 })

+ 3 - 3
tooling/cli/node/test/jest/fixtures/app/dist/index.html

@@ -23,10 +23,10 @@
       }
 
       function testFs(dir) {
-        var contents = 'TAURI E2E TEST FILE'
-        var commandSuffix = dir ? 'WithDir' : ''
+        const contents = 'TAURI E2E TEST FILE'
+        const commandSuffix = dir ? 'WithDir' : ''
 
-        var options = {
+        const options = {
           dir: dir || null
         }
 

+ 1 - 1
tooling/cli/node/test/jest/fixtures/app/src-tauri/Cargo.toml

@@ -24,7 +24,7 @@ icon = [
 tauri-build = { path = "../../../../../../../../core/tauri-build", features = [] }
 
 [dependencies]
-serde_json = "1.0.105"
+serde_json = "1.0.107"
 serde = "1.0"
 serde_derive = "1.0"
 tauri = { path = "../../../../../../../../core/tauri", features = ["api-all"] }

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 265 - 454
tooling/cli/node/yarn.lock


+ 65 - 8
tooling/cli/schema.json

@@ -934,13 +934,14 @@
         },
         "resources": {
           "description": "App resources to bundle. Each resource is a path to a file or directory. Glob patterns are supported.",
-          "type": [
-            "array",
-            "null"
-          ],
-          "items": {
-            "type": "string"
-          }
+          "anyOf": [
+            {
+              "$ref": "#/definitions/BundleResources"
+            },
+            {
+              "type": "null"
+            }
+          ]
         },
         "copyright": {
           "description": "A copyright string associated with your application.",
@@ -1163,6 +1164,25 @@
         }
       ]
     },
+    "BundleResources": {
+      "description": "Definition for bundle resources. Can be either a list of paths to include or a map of source to target paths.",
+      "anyOf": [
+        {
+          "description": "A list of paths to include.",
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        },
+        {
+          "description": "A map of source to target paths.",
+          "type": "object",
+          "additionalProperties": {
+            "type": "string"
+          }
+        }
+      ]
+    },
     "FileAssociation": {
       "description": "File association",
       "type": "object",
@@ -1753,6 +1773,17 @@
           "description": "Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not. By default the OS language is selected, with a fallback to the first language in the `languages` array.",
           "default": false,
           "type": "boolean"
+        },
+        "compression": {
+          "description": "Set the compression algorithm used to compress files in the installer.\n\nSee <https://nsis.sourceforge.io/Reference/SetCompressor>",
+          "anyOf": [
+            {
+              "$ref": "#/definitions/NsisCompression"
+            },
+            {
+              "type": "null"
+            }
+          ]
         }
       },
       "additionalProperties": false
@@ -1783,6 +1814,32 @@
         }
       ]
     },
+    "NsisCompression": {
+      "description": "Compression algorithms used in the NSIS installer.\n\nSee <https://nsis.sourceforge.io/Reference/SetCompressor>",
+      "oneOf": [
+        {
+          "description": "ZLIB uses the deflate algorithm, it is a quick and simple method. With the default compression level it uses about 300 KB of memory.",
+          "type": "string",
+          "enum": [
+            "zlib"
+          ]
+        },
+        {
+          "description": "BZIP2 usually gives better compression ratios than ZLIB, but it is a bit slower and uses more memory. With the default compression level it uses about 4 MB of memory.",
+          "type": "string",
+          "enum": [
+            "bzip2"
+          ]
+        },
+        {
+          "description": "LZMA (default) is a new compression method that gives very good compression ratios. The decompression speed is high (10-20 MB/s on a 2 GHz CPU), the compression speed is lower. The memory size that will be used for decompression is the dictionary size plus a few KBs, the default is 8 MB.",
+          "type": "string",
+          "enum": [
+            "lzma"
+          ]
+        }
+      ]
+    },
     "IosConfig": {
       "description": "General configuration for the iOS target.",
       "type": "object",
@@ -2019,7 +2076,7 @@
           }
         },
         "plugins": {
-          "description": "The list of plugins that are allowed in this scope.",
+          "description": "The list of plugins that are allowed in this scope. The names should be without the `tauri-plugin-` prefix, for example `\"store\"` for `tauri-plugin-store`.",
           "default": [],
           "type": "array",
           "items": {

+ 1 - 0
tooling/cli/src/add.rs

@@ -90,6 +90,7 @@ pub fn command(options: Options) -> Result<()> {
         PackageManager::Pnpm => cross_command("pnpm"),
         PackageManager::Yarn => cross_command("yarn"),
         PackageManager::YarnBerry => cross_command("yarn"),
+        PackageManager::Bun => cross_command("bun"),
       };
 
       cmd.arg("add").arg(&npm_spec);

+ 4 - 4
tooling/cli/src/build.rs

@@ -203,11 +203,11 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
           // sign our path from environment variables
           let (signature_path, signature) = sign_file(&secret_key, path)?;
           if signature.keynum() != public_key.keynum() {
-            return Err(anyhow::anyhow!(
-              "The updater secret key from `TAURI_SIGNING_PRIVATE_KEY` does not match the public key defined in `tauri.conf.json > tauri > updater > pubkey`."
-            ));
+            log::warn!(
+              "The updater secret key from `TAURI_PRIVATE_KEY` does not match the public key defined in `tauri.conf.json > tauri > updater > pubkey`. If you are not rotating keys, this means your configuration is wrong and won't be accepted at runtime."
+            );
           }
-          signed_paths.append(&mut vec![signature_path]);
+          signed_paths.push(signature_path);
         }
       }
 

+ 4 - 2
tooling/cli/src/completions.rs

@@ -10,7 +10,7 @@ use log::info;
 
 use std::{fs::write, path::PathBuf};
 
-const PKG_MANAGERS: &[&str] = &["cargo", "pnpm", "npm", "yarn"];
+const PKG_MANAGERS: &[&str] = &["cargo", "pnpm", "npm", "yarn", "bun"];
 
 #[derive(Debug, Clone, Parser)]
 #[clap(about = "Generate Tauri CLI shell completions for Bash, Zsh, PowerShell or Fish")]
@@ -25,7 +25,7 @@ pub struct Options {
 
 fn completions_for(shell: Shell, manager: &'static str, cmd: Command) -> Vec<u8> {
   let tauri = cmd.name("tauri");
-  let mut command = if manager == "npm" {
+  let mut command = if manager == "npm" || manager == "bun" {
     Command::new(manager)
       .bin_name(manager)
       .subcommand(Command::new("run").subcommand(tauri))
@@ -47,6 +47,8 @@ fn get_completions(shell: Shell, cmd: Command) -> Result<String> {
         "complete -F _cargo -o bashdefault -o default {} tauri\n",
         if manager == &"npm" {
           "npm run"
+        } else if manager == &"bun" {
+          "bun run"
         } else {
           manager
         }

+ 21 - 4
tooling/cli/src/helpers/app_paths.rs

@@ -68,10 +68,21 @@ fn lookup<F: Fn(&PathBuf) -> bool>(dir: &Path, checker: F) -> Option<PathBuf> {
 fn get_tauri_dir() -> PathBuf {
   let cwd = current_dir().expect("failed to read cwd");
 
-  if cwd.join("src-tauri/tauri.conf.json").exists()
-    || cwd.join("src-tauri/tauri.conf.json5").exists()
+  if cwd.join(ConfigFormat::Json.into_file_name()).exists()
+    || cwd.join(ConfigFormat::Json5.into_file_name()).exists()
+    || cwd.join(ConfigFormat::Toml.into_file_name()).exists()
   {
-    return cwd.join("src-tauri/");
+    return cwd;
+  }
+
+  let src_tauri = cwd.join("src-tauri");
+  if src_tauri.join(ConfigFormat::Json.into_file_name()).exists()
+    || src_tauri
+      .join(ConfigFormat::Json5.into_file_name())
+      .exists()
+    || src_tauri.join(ConfigFormat::Toml.into_file_name()).exists()
+  {
+    return src_tauri;
   }
 
   lookup(&cwd, |path| folder_has_configuration_file(Target::Linux, path) || is_configuration_file(Target::Linux, path))
@@ -86,7 +97,13 @@ fn get_tauri_dir() -> PathBuf {
 }
 
 fn get_app_dir() -> Option<PathBuf> {
-  lookup(&current_dir().expect("failed to read cwd"), |path| {
+  let cwd = current_dir().expect("failed to read cwd");
+
+  if cwd.join("package.json").exists() {
+    return Some(cwd);
+  }
+
+  lookup(&cwd, |path| {
     if let Some(file_name) = path.file_name() {
       file_name == OsStr::new("package.json")
     } else {

+ 5 - 5
tooling/cli/src/helpers/auto-reload.js

@@ -5,21 +5,21 @@
 // taken from https://github.com/thedodd/trunk/blob/5c799dc35f1f1d8f8d3d30c8723cbb761a9b6a08/src/autoreload.js
 
 ;(function () {
-  var url = '{{reload_url}}'
-  var poll_interval = 5000
-  var reload_upon_connect = () => {
+  const url = '{{reload_url}}'
+  const poll_interval = 5000
+  const reload_upon_connect = () => {
     window.setTimeout(() => {
       // when we successfully reconnect, we'll force a
       // reload (since we presumably lost connection to
       // trunk due to it being killed, so it will have
       // rebuilt on restart)
-      var ws = new WebSocket(url)
+      const ws = new WebSocket(url)
       ws.onopen = () => window.location.reload()
       ws.onclose = reload_upon_connect
     }, poll_interval)
   }
 
-  var ws = new WebSocket(url)
+  const ws = new WebSocket(url)
   ws.onmessage = (ev) => {
     const msg = JSON.parse(ev.data)
     if (msg.reload) {

+ 1 - 0
tooling/cli/src/helpers/config.rs

@@ -110,6 +110,7 @@ pub fn nsis_settings(config: NsisConfig) -> tauri_bundler::NsisSettings {
     languages: config.languages,
     custom_language_files: config.custom_language_files,
     display_language_selector: config.display_language_selector,
+    compression: config.compression,
   }
 }
 

+ 10 - 0
tooling/cli/src/helpers/npm.rs

@@ -11,6 +11,7 @@ pub enum PackageManager {
   Pnpm,
   Yarn,
   YarnBerry,
+  Bun,
 }
 
 impl Display for PackageManager {
@@ -23,6 +24,7 @@ impl Display for PackageManager {
         PackageManager::Pnpm => "pnpm",
         PackageManager::Yarn => "yarn",
         PackageManager::YarnBerry => "yarn berry",
+        PackageManager::Bun => "bun",
       }
     )
   }
@@ -101,6 +103,14 @@ impl PackageManager {
           .status()
           .map_err(Into::into)
       }
+      PackageManager::Bun => {
+        let mut cmd = cross_command("bun");
+        cmd
+          .arg("install")
+          .args(dependencies)
+          .status()
+          .map_err(Into::into)
+      }
     }
   }
 }

+ 1 - 1
tooling/cli/src/helpers/web_dev_server.rs

@@ -52,7 +52,7 @@ pub fn start_dev_server<P: AsRef<Path>>(
         let serve_dir_ = serve_dir.clone();
         thread::spawn(move || {
           let (tx, rx) = sync_channel(1);
-          let mut watcher = new_debouncer(Duration::from_secs(1), None, move |r| {
+          let mut watcher = new_debouncer(Duration::from_secs(1), move |r| {
             if let Ok(events) = r {
               tx.send(events).unwrap()
             }

+ 1 - 2
tooling/cli/src/icon.rs

@@ -41,8 +41,7 @@ struct PngEntry {
 #[derive(Debug, Parser)]
 #[clap(about = "Generate various icons for all major platforms")]
 pub struct Options {
-  // TODO: Confirm 1240px
-  /// Path to the source icon (png, 1240x1240px with transparency).
+  /// Path to the source icon (png, 1024x1024px with transparency).
   #[clap(default_value = "./app-icon.png")]
   input: PathBuf,
   /// Output directory.

+ 13 - 35
tooling/cli/src/info/app.rs

@@ -2,7 +2,7 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::{SectionItem, Status};
+use super::SectionItem;
 use crate::helpers::framework;
 use std::{
   fs::read_to_string,
@@ -18,15 +18,11 @@ pub fn items(app_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec<Section
       let config = config_guard.as_ref().unwrap();
 
       let bundle_or_build = if config.tauri.bundle.active {
-        "bundle".to_string()
+        "bundle"
       } else {
-        "build".to_string()
+        "build"
       };
-      items.push(SectionItem::new(
-        move || Some((format!("build-type: {bundle_or_build}"), Status::Neutral)),
-        || None,
-        false,
-      ));
+      items.push(SectionItem::new().description(format!("build-type: {bundle_or_build}")));
 
       let csp = config
         .tauri
@@ -35,42 +31,24 @@ pub fn items(app_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec<Section
         .clone()
         .map(|c| c.to_string())
         .unwrap_or_else(|| "unset".to_string());
-      items.push(SectionItem::new(
-        move || Some((format!("CSP: {csp}"), Status::Neutral)),
-        || None,
-        false,
-      ));
+      items.push(SectionItem::new().description(format!("CSP: {csp}")));
 
-      let dist_dir = config.build.dist_dir.to_string();
-      items.push(SectionItem::new(
-        move || Some((format!("distDir: {dist_dir}"), Status::Neutral)),
-        || None,
-        false,
-      ));
+      let dist_dir = &config.build.dist_dir;
+      items.push(SectionItem::new().description(format!("distDir: {dist_dir}")));
 
-      let dev_path = config.build.dev_path.to_string();
-      items.push(SectionItem::new(
-        move || Some((format!("devPath: {dev_path}"), Status::Neutral)),
-        || None,
-        false,
-      ));
+      let dev_path = &config.build.dev_path;
+      items.push(SectionItem::new().description(format!("devPath: {dev_path}")));
 
       if let Some(app_dir) = app_dir {
         if let Ok(package_json) = read_to_string(app_dir.join("package.json")) {
           let (framework, bundler) = framework::infer_from_package_json(&package_json);
+
           if let Some(framework) = framework {
-            items.push(SectionItem::new(
-              move || Some((format!("framework: {framework}"), Status::Neutral)),
-              || None,
-              false,
-            ));
+            items.push(SectionItem::new().description(format!("framework: {framework}")));
           }
+
           if let Some(bundler) = bundler {
-            items.push(SectionItem::new(
-              move || Some((format!("bundler: {bundler}"), Status::Neutral)),
-              || None,
-              false,
-            ));
+            items.push(SectionItem::new().description(format!("bundler: {bundler}")));
           }
         }
       }

+ 53 - 103
tooling/cli/src/info/env_nodejs.rs

@@ -2,14 +2,13 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::VersionMetadata;
-use super::{SectionItem, Status};
+use super::{ActionResult, SectionItem, VersionMetadata};
 use colored::Colorize;
 
 use crate::helpers::cross_command;
 
-pub fn items(metadata: &VersionMetadata) -> (Vec<SectionItem>, Option<String>) {
-  let yarn_version = cross_command("yarn")
+pub fn manager_version(package_manager: &str) -> Option<String> {
+  cross_command(package_manager)
     .arg("-v")
     .output()
     .map(|o| {
@@ -21,107 +20,58 @@ pub fn items(metadata: &VersionMetadata) -> (Vec<SectionItem>, Option<String>) {
       }
     })
     .ok()
-    .unwrap_or_default();
-  let yarn_version_c = yarn_version.clone();
+    .unwrap_or_default()
+}
+
+pub fn items(metadata: &VersionMetadata) -> Vec<SectionItem> {
   let node_target_ver = metadata.js_cli.node.replace(">= ", "");
 
-  (
-    vec![
-      SectionItem::new(
-        move || {
-          cross_command("node")
-            .arg("-v")
-            .output()
-            .map(|o| {
-              if o.status.success() {
-                let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string();
-                let v = v
-                  .split('\n')
-                  .next()
-                  .unwrap()
-                  .strip_prefix('v')
-                  .unwrap_or_default()
-                  .trim();
-                Some((
-                  format!("node: {}{}", v, {
-                    let version = semver::Version::parse(v).unwrap();
-                    let target_version = semver::Version::parse(node_target_ver.as_str()).unwrap();
-                    if version < target_version {
-                      format!(
-                        " ({}, latest: {})",
-                        "outdated".red(),
-                        target_version.to_string().green()
-                      )
-                    } else {
-                      "".into()
-                    }
-                  }),
-                  Status::Neutral,
-                ))
-              } else {
-                None
-              }
-            })
-            .ok()
-            .unwrap_or_default()
-        },
-        || None,
-        false,
-      ),
-      SectionItem::new(
-        || {
-          cross_command("pnpm")
-            .arg("-v")
-            .output()
-            .map(|o| {
-              if o.status.success() {
-                let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string();
-                Some((
-                  format!("pnpm: {}", v.split('\n').next().unwrap()),
-                  Status::Neutral,
-                ))
-              } else {
-                None
-              }
-            })
-            .ok()
-            .unwrap_or_default()
-        },
-        || None,
-        false,
-      ),
-      SectionItem::new(
-        move || {
-          yarn_version_c
-            .as_ref()
-            .map(|v| (format!("yarn: {v}"), Status::Neutral))
-        },
-        || None,
-        false,
-      ),
-      SectionItem::new(
-        || {
-          cross_command("npm")
-            .arg("-v")
-            .output()
-            .map(|o| {
-              if o.status.success() {
-                let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string();
-                Some((
-                  format!("npm: {}", v.split('\n').next().unwrap()),
-                  Status::Neutral,
-                ))
+  vec![
+    SectionItem::new().action(move || {
+      cross_command("node")
+        .arg("-v")
+        .output()
+        .map(|o| {
+          if o.status.success() {
+            let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string();
+            let v = v
+              .split('\n')
+              .next()
+              .unwrap()
+              .strip_prefix('v')
+              .unwrap_or_default()
+              .trim();
+            ActionResult::Description(format!("node: {}{}", v, {
+              let version = semver::Version::parse(v).unwrap();
+              let target_version = semver::Version::parse(node_target_ver.as_str()).unwrap();
+              if version < target_version {
+                format!(
+                  " ({}, latest: {})",
+                  "outdated".red(),
+                  target_version.to_string().green()
+                )
               } else {
-                None
+                "".into()
               }
-            })
-            .ok()
-            .unwrap_or_default()
-        },
-        || None,
-        false,
-      ),
-    ],
-    yarn_version,
-  )
+            }))
+          } else {
+            ActionResult::None
+          }
+        })
+        .ok()
+        .unwrap_or_default()
+    }),
+    SectionItem::new().action(|| {
+      manager_version("pnpm")
+        .map(|v| format!("pnpm: {}", v))
+        .into()
+    }),
+    SectionItem::new().action(|| {
+      manager_version("yarn")
+        .map(|v| format!("yarn: {}", v))
+        .into()
+    }),
+    SectionItem::new().action(|| manager_version("npm").map(|v| format!("npm: {}", v)).into()),
+    SectionItem::new().action(|| manager_version("bun").map(|v| format!("bun: {}", v)).into()),
+  ]
 }

+ 50 - 100
tooling/cli/src/info/env_rust.rs

@@ -7,95 +7,55 @@ use super::Status;
 use colored::Colorize;
 use std::process::Command;
 
+fn component_version(component: &str) -> Option<(String, Status)> {
+  Command::new(component)
+    .arg("-V")
+    .output()
+    .map(|o| String::from_utf8_lossy(o.stdout.as_slice()).to_string())
+    .map(|v| {
+      format!(
+        "{component}: {}",
+        v.split('\n')
+          .next()
+          .unwrap()
+          .strip_prefix(&format!("{component} "))
+          .unwrap_or_default()
+      )
+    })
+    .map(|desc| (desc, Status::Success))
+    .ok()
+}
+
 pub fn items() -> Vec<SectionItem> {
   vec![
-    SectionItem::new(
-      || {
-        Some(
-          Command::new("rustc")
-            .arg("-V")
-            .output()
-            .map(|o| String::from_utf8_lossy(o.stdout.as_slice()).to_string())
-            .map(|v| {
-              format!(
-                "rustc: {}",
-                v.split('\n')
-                  .next()
-                  .unwrap()
-                  .strip_prefix("rustc ")
-                  .unwrap_or_default()
-              )
-            })
-            .map(|desc| (desc, Status::Success))
-            .ok()
-            .unwrap_or_else(|| {
-              (
-                format!(
-                  "rustc: {}\nMaybe you don't have rust installed! Visit {}",
-                  "not installed!".red(),
-                  "https://rustup.rs/".cyan()
-                ),
-                Status::Error,
-              )
-            }),
-        )
-      },
-      || None,
-      false,
-    ),
-    SectionItem::new(
-      || {
-        Some(
-          Command::new("cargo")
-            .arg("-V")
-            .output()
-            .map(|o| String::from_utf8_lossy(o.stdout.as_slice()).to_string())
-            .map(|v| {
+    SectionItem::new().action(|| {
+       component_version("rustc")
+          .unwrap_or_else(|| {
+            (
               format!(
-                "Cargo: {}",
-                v.split('\n')
-                  .next()
-                  .unwrap()
-                  .strip_prefix("cargo ")
-                  .unwrap_or_default()
-              )
-            })
-            .map(|desc| (desc, Status::Success))
-            .ok()
-            .unwrap_or_else(|| {
-              (
-                format!(
-                  "Cargo: {}\nMaybe you don't have rust installed! Visit {}",
-                  "not installed!".red(),
-                  "https://rustup.rs/".cyan()
-                ),
-                Status::Error,
-              )
-            }),
-        )
-      },
-      || None,
-      false,
-    ),
-    SectionItem::new(
-      || {
-        Some(
-          Command::new("rustup")
-            .arg("-V")
-            .output()
-            .map(|o| String::from_utf8_lossy(o.stdout.as_slice()).to_string())
-            .map(|v| {
+                "rustc: {}\nMaybe you don't have rust installed! Visit {}",
+                "not installed!".red(),
+                "https://rustup.rs/".cyan()
+              ),
+              Status::Error,
+            )
+          }).into()
+    }),
+    SectionItem::new().action(|| {
+        component_version("cargo")
+          .unwrap_or_else(|| {
+            (
               format!(
-                "rustup: {}",
-                v.split('\n')
-                  .next()
-                  .unwrap()
-                  .strip_prefix("rustup ")
-                  .unwrap_or_default()
-              )
-            })
-            .map(|desc| (desc, Status::Success))
-            .ok()
+                "Cargo: {}\nMaybe you don't have rust installed! Visit {}",
+                "not installed!".red(),
+                "https://rustup.rs/".cyan()
+              ),
+              Status::Error,
+            )
+          }).into()
+    }),
+    SectionItem::new().action(|| {
+        component_version("rustup")
             .unwrap_or_else(|| {
               (
                 format!(
@@ -105,15 +65,9 @@ pub fn items() -> Vec<SectionItem> {
                 ),
                 Status::Warning,
               )
-            }),
-        )
-      },
-      || None,
-      false,
-    ),
-    SectionItem::new(
-      || {
-        Some(
+            }).into()
+    }),
+    SectionItem::new().action(|| {
           Command::new("rustup")
             .args(["show", "active-toolchain"])
             .output()
@@ -135,11 +89,7 @@ pub fn items() -> Vec<SectionItem> {
                 ),
                 Status::Warning,
               )
-            }),
-        )
-      },
-      || None,
-      false,
-    ),
+            }).into()
+    }),
   ]
 }

+ 50 - 83
tooling/cli/src/info/env_system.rs

@@ -2,8 +2,7 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::SectionItem;
-use super::Status;
+use super::{SectionItem, Status};
 use colored::Colorize;
 #[cfg(windows)]
 use serde::Deserialize;
@@ -177,74 +176,55 @@ fn is_xcode_command_line_tools_installed() -> bool {
 
 pub fn items() -> Vec<SectionItem> {
   vec![
-    SectionItem::new(
-      || {
-        let os_info = os_info::get();
-        Some((
-          format!(
-            "OS: {} {} {:?}",
-            os_info.os_type(),
-            os_info.version(),
-            os_info.bitness()
-          ),
-          Status::Neutral,
-        ))
-      },
-      || None,
-      false,
-    ),
+    SectionItem::new().action(|| {
+      let os_info = os_info::get();
+      format!(
+        "OS: {} {} {:?}",
+        os_info.os_type(),
+        os_info.version(),
+        os_info.bitness()
+      ).into()
+    }),
     #[cfg(windows)]
-    SectionItem::new(
-      || {
-        let error = || {
-          format!(
-            "Webview2: {}\nVisit {}",
-            "not installed!".red(),
-            "https://developer.microsoft.com/en-us/microsoft-edge/webview2/".cyan()
-          )
-        };
-        Some(
-          webview2_version()
-            .map(|v| {
-              v.map(|v| (format!("WebView2: {}", v), Status::Success))
-                .unwrap_or_else(|| (error(), Status::Error))
-            })
-            .unwrap_or_else(|_| (error(), Status::Error)),
-        )
-      },
-      || None,
-      false,
-    ),
+    SectionItem::new().action(|| {
+      let error = format!(
+          "Webview2: {}\nVisit {}",
+          "not installed!".red(),
+          "https://developer.microsoft.com/en-us/microsoft-edge/webview2/".cyan()
+        );
+      webview2_version()
+        .map(|v| {
+          v.map(|v| (format!("WebView2: {}", v), Status::Success))
+            .unwrap_or_else(|| (error.clone(), Status::Error))
+        })
+        .unwrap_or_else(|_| (error, Status::Error)).into()
+    }),
     #[cfg(windows)]
-    SectionItem::new(
-      || {
-        let build_tools = build_tools_version().unwrap_or_default();
-        if build_tools.is_empty() {
-          Some((
+    SectionItem::new().action(|| {
+      let build_tools = build_tools_version().unwrap_or_default();
+      if build_tools.is_empty() {
+        (
             format!(
               "Couldn't detect any Visual Studio or VS Build Tools instance with MSVC and SDK components. Download from {}",
               "https://aka.ms/vs/17/release/vs_BuildTools.exe".cyan()
             ),
             Status::Error,
-          ))
-        } else {
-          Some((
-            format!(
-              "MSVC: {}{}",
-              if build_tools.len() > 1 {
-                format!("\n  {} ", "-".cyan())
-              } else {
-                "".into()
-              },
-              build_tools.join(format!("\n  {} ", "-".cyan()).as_str()),
-            ),
-            Status::Success,
-          ))
-        }
-      },
-      || None,
-      false,
-    ),
+          ).into()
+      } else {
+        (
+          format!(
+            "MSVC: {}{}",
+            if build_tools.len() > 1 {
+              format!("\n  {} ", "-".cyan())
+            } else {
+              "".into()
+            },
+            build_tools.join(format!("\n  {} ", "-".cyan()).as_str()),
+          ),
+          Status::Success,
+        ).into()
+      }
+    }),
     #[cfg(any(
       target_os = "linux",
       target_os = "dragonfly",
@@ -252,9 +232,7 @@ pub fn items() -> Vec<SectionItem> {
       target_os = "openbsd",
       target_os = "netbsd"
     ))]
-    SectionItem::new(
-      || {
-        Some(
+    SectionItem::new().action(|| {
           webkit2gtk_ver()
             .map(|v| (format!("webkit2gtk-4.1: {v}"), Status::Success))
             .unwrap_or_else(|| {
@@ -266,11 +244,8 @@ pub fn items() -> Vec<SectionItem> {
                 ),
                 Status::Error,
               )
-            }),
-        )
+            }).into()
       },
-      || None,
-      false,
     ),
     #[cfg(any(
       target_os = "linux",
@@ -279,9 +254,7 @@ pub fn items() -> Vec<SectionItem> {
       target_os = "openbsd",
       target_os = "netbsd"
     ))]
-    SectionItem::new(
-      || {
-        Some(
+    SectionItem::new().action(|| {
           rsvg2_ver()
             .map(|v| (format!("rsvg2: {v}"), Status::Success))
             .unwrap_or_else(|| {
@@ -293,16 +266,12 @@ pub fn items() -> Vec<SectionItem> {
                 ),
                 Status::Error,
               )
-            }),
-        )
+            }).into()
       },
-      || None,
-      false,
     ),
     #[cfg(target_os = "macos")]
-    SectionItem::new(
-      || {
-        Some(if is_xcode_command_line_tools_installed() {
+    SectionItem::new().action(|| {
+        if is_xcode_command_line_tools_installed() {
           (
             "Xcode Command Line Tools: installed".into(),
             Status::Success,
@@ -316,10 +285,8 @@ pub fn items() -> Vec<SectionItem> {
             ),
             Status::Error,
           )
-        })
+        }.into()
       },
-      || None,
-      false,
     ),
   ]
 }

+ 18 - 23
tooling/cli/src/info/ios.rs

@@ -2,31 +2,26 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::{SectionItem, Status};
+use super::SectionItem;
 
 use colored::Colorize;
 
 pub fn items() -> Vec<SectionItem> {
-  vec![SectionItem::new(
-    || {
-      let teams = cargo_mobile2::apple::teams::find_development_teams().unwrap_or_default();
-      Some((
-        if teams.is_empty() {
-          "Developer Teams: None".red().to_string()
-        } else {
-          format!(
-            "Developer Teams: {}",
-            teams
-              .iter()
-              .map(|t| format!("{} (ID: {})", t.name, t.id))
-              .collect::<Vec<String>>()
-              .join(", ")
-          )
-        },
-        Status::Neutral,
-      ))
-    },
-    || None,
-    false,
-  )]
+  vec![SectionItem::new().action(|| {
+    let teams = cargo_mobile2::apple::teams::find_development_teams().unwrap_or_default();
+
+    if teams.is_empty() {
+      "Developer Teams: None".red().to_string().into()
+    } else {
+      format!(
+        "Developer Teams: {}",
+        teams
+          .iter()
+          .map(|t| format!("{} (ID: {})", t.name, t.id))
+          .collect::<Vec<String>>()
+          .join(", ")
+      )
+      .into()
+    }
+  })]
 }

+ 116 - 34
tooling/cli/src/info/mod.rs

@@ -4,7 +4,7 @@
 
 use crate::Result;
 use clap::Parser;
-use colored::Colorize;
+use colored::{ColoredString, Colorize};
 use dialoguer::{theme::ColorfulTheme, Confirm};
 use serde::Deserialize;
 use std::{
@@ -82,6 +82,18 @@ pub enum Status {
   Error,
 }
 
+impl Status {
+  fn color<S: AsRef<str>>(&self, s: S) -> ColoredString {
+    let s = s.as_ref();
+    match self {
+      Status::Neutral => s.normal(),
+      Status::Success => s.green(),
+      Status::Warning => s.yellow(),
+      Status::Error => s.red(),
+    }
+  }
+}
+
 impl Display for Status {
   fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
     write!(
@@ -97,15 +109,55 @@ impl Display for Status {
   }
 }
 
+#[derive(Default)]
+pub enum ActionResult {
+  Full {
+    description: String,
+    status: Status,
+  },
+  Description(String),
+  #[default]
+  None,
+}
+
+impl From<String> for ActionResult {
+  fn from(value: String) -> Self {
+    ActionResult::Description(value)
+  }
+}
+
+impl From<(String, Status)> for ActionResult {
+  fn from(value: (String, Status)) -> Self {
+    ActionResult::Full {
+      description: value.0,
+      status: value.1,
+    }
+  }
+}
+
+impl From<Option<String>> for ActionResult {
+  fn from(value: Option<String>) -> Self {
+    value.map(ActionResult::Description).unwrap_or_default()
+  }
+}
+
+impl From<Option<(String, Status)>> for ActionResult {
+  fn from(value: Option<(String, Status)>) -> Self {
+    value
+      .map(|v| ActionResult::Full {
+        description: v.0,
+        status: v.1,
+      })
+      .unwrap_or_default()
+  }
+}
+
 pub struct SectionItem {
   /// If description is none, the item is skipped
   description: Option<String>,
   status: Status,
-  /// This closure return will be assigned to status and description
-  action: Box<dyn FnMut() -> Option<(String, Status)>>,
-  /// This closure return will be assigned to status and description
-  action_if_err: Box<dyn FnMut() -> Option<(String, Status)>>,
-  has_action_if_err: bool,
+  action: Option<Box<dyn FnMut() -> ActionResult>>,
+  action_if_err: Option<Box<dyn FnMut() -> ActionResult>>,
 }
 
 impl Display for SectionItem {
@@ -121,29 +173,66 @@ impl Display for SectionItem {
 }
 
 impl SectionItem {
-  fn new<
-    F1: FnMut() -> Option<(String, Status)> + 'static,
-    F2: FnMut() -> Option<(String, Status)> + 'static,
-  >(
-    action: F1,
-    action_if_err: F2,
-    has_action_if_err: bool,
-  ) -> Self {
+  fn new() -> Self {
     Self {
-      action: Box::new(action),
-      action_if_err: Box::new(action_if_err),
-      has_action_if_err,
+      action: None,
+      action_if_err: None,
       description: None,
       status: Status::Neutral,
     }
   }
-  fn run(&mut self, interactive: bool) -> Status {
-    if let Some(ret) = (self.action)() {
-      self.description = Some(ret.0);
-      self.status = ret.1;
+
+  fn action<F: FnMut() -> ActionResult + 'static>(mut self, action: F) -> Self {
+    self.action = Some(Box::new(action));
+    self
+  }
+
+  // fn action_if_err<F: FnMut() -> ActionResult + 'static>(mut self, action: F) -> Self {
+  //   self.action_if_err = Some(Box::new(action));
+  //   self
+  // }
+
+  fn description<S: AsRef<str>>(mut self, description: S) -> Self {
+    self.description = Some(description.as_ref().to_string());
+    self
+  }
+
+  fn run_action(&mut self) {
+    let mut res = ActionResult::None;
+    if let Some(action) = &mut self.action {
+      res = action();
     }
+    self.apply_action_result(res);
+  }
 
-    if self.status == Status::Error && interactive && self.has_action_if_err {
+  fn run_action_if_err(&mut self) {
+    let mut res = ActionResult::None;
+    if let Some(action) = &mut self.action_if_err {
+      res = action();
+    }
+    self.apply_action_result(res);
+  }
+
+  fn apply_action_result(&mut self, result: ActionResult) {
+    match result {
+      ActionResult::Full {
+        description,
+        status,
+      } => {
+        self.description = Some(description);
+        self.status = status;
+      }
+      ActionResult::Description(description) => {
+        self.description = Some(description);
+      }
+      ActionResult::None => {}
+    }
+  }
+
+  fn run(&mut self, interactive: bool) -> Status {
+    self.run_action();
+
+    if self.status == Status::Error && interactive && self.action_if_err.is_some() {
       if let Some(description) = &self.description {
         let confirmed = Confirm::with_theme(&ColorfulTheme::default())
           .with_prompt(format!(
@@ -153,13 +242,11 @@ impl SectionItem {
           .interact()
           .unwrap_or(false);
         if confirmed {
-          if let Some(ret) = (self.action_if_err)() {
-            self.description = Some(ret.0);
-            self.status = ret.1;
-          }
+          self.run_action_if_err()
         }
       }
     }
+
     self.status
   }
 }
@@ -182,12 +269,7 @@ impl Section<'_> {
     }
 
     let status_str = format!("[{status}]");
-    let status = match status {
-      Status::Neutral => status_str.normal(),
-      Status::Success => status_str.green(),
-      Status::Warning => status_str.yellow(),
-      Status::Error => status_str.red(),
-    };
+    let status = status.color(status_str);
 
     println!();
     println!("{} {}", status, self.label.bold().yellow());
@@ -231,7 +313,7 @@ pub fn command(options: Options) -> Result<()> {
   };
   environment.items.extend(env_system::items());
   environment.items.extend(env_rust::items());
-  let (items, yarn_version) = env_nodejs::items(&metadata);
+  let items = env_nodejs::items(&metadata);
   environment.items.extend(items);
 
   let mut packages = Section {
@@ -244,7 +326,7 @@ pub fn command(options: Options) -> Result<()> {
     .extend(packages_rust::items(app_dir, tauri_dir.as_deref()));
   packages
     .items
-    .extend(packages_nodejs::items(app_dir, &metadata, yarn_version));
+    .extend(packages_nodejs::items(app_dir, &metadata));
 
   let mut app = Section {
     label: "App",

+ 126 - 68
tooling/cli/src/info/packages_nodejs.rs

@@ -2,8 +2,8 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::VersionMetadata;
-use super::{SectionItem, Status};
+use super::SectionItem;
+use super::{env_nodejs::manager_version, VersionMetadata};
 use colored::Colorize;
 use serde::Deserialize;
 use std::path::{Path, PathBuf};
@@ -72,6 +72,18 @@ fn npm_latest_version(pm: &PackageManager, name: &str) -> crate::Result<Option<S
         Ok(None)
       }
     }
+    // Bun doesn't support `info` command
+    PackageManager::Bun => {
+      let mut cmd = cross_command("npm");
+
+      let output = cmd.arg("show").arg(name).arg("version").output()?;
+      if output.status.success() {
+        let stdout = String::from_utf8_lossy(&output.stdout);
+        Ok(Some(stdout.replace('\n', "")))
+      } else {
+        Ok(None)
+      }
+    }
   }
 }
 
@@ -117,6 +129,16 @@ fn npm_package_version<P: AsRef<Path>>(
         .output()?,
       None,
     ),
+    // Bun doesn't support `list` command
+    PackageManager::Bun => (
+      cross_command("npm")
+        .arg("list")
+        .arg(name)
+        .args(["version", "--depth", "0"])
+        .current_dir(app_dir)
+        .output()?,
+      None,
+    ),
   };
   if output.status.success() {
     let stdout = String::from_utf8_lossy(&output.stdout);
@@ -132,39 +154,81 @@ fn npm_package_version<P: AsRef<Path>>(
   }
 }
 
-pub fn items(
-  app_dir: Option<&PathBuf>,
-  metadata: &VersionMetadata,
-  yarn_version: Option<String>,
-) -> Vec<SectionItem> {
-  let package_managers = app_dir
-    .map(PackageManager::from_project)
-    .unwrap_or_else(|| {
-      println!(
-        "{}: no lock files found, defaulting to npm",
-        "WARNING".yellow()
-      );
-      vec![PackageManager::Npm]
-    });
-
-  let mut package_manager = if package_managers.len() > 1 {
-    let pkg_manager = package_managers[0];
+fn get_package_manager<T: AsRef<str>>(app_dir_entries: &[T]) -> PackageManager {
+  let mut use_npm = false;
+  let mut use_pnpm = false;
+  let mut use_yarn = false;
+  let mut use_bun = false;
+
+  for name in app_dir_entries {
+    if name.as_ref() == "package-lock.json" {
+      use_npm = true;
+    } else if name.as_ref() == "pnpm-lock.yaml" {
+      use_pnpm = true;
+    } else if name.as_ref() == "yarn.lock" {
+      use_yarn = true;
+    } else if name.as_ref() == "bun.lockb" {
+      use_bun = true;
+    }
+  }
+
+  if !use_npm && !use_pnpm && !use_yarn && !use_bun {
+    println!(
+      "{}: no lock files found, defaulting to npm",
+      "WARNING".yellow()
+    );
+    return PackageManager::Npm;
+  }
+
+  let mut found = Vec::new();
+
+  if use_npm {
+    found.push(PackageManager::Npm);
+  }
+  if use_pnpm {
+    found.push(PackageManager::Pnpm);
+  }
+  if use_yarn {
+    found.push(PackageManager::Yarn);
+  }
+  if use_bun {
+    found.push(PackageManager::Bun);
+  }
+
+  if found.len() > 1 {
+    let pkg_manger = found[0];
     println!(
-          "{}: Only one package manager should be used, but found {}.\n         Please remove unused package manager lock files, will use {} for now!",
-          "WARNING".yellow(),
-          package_managers.iter().map(ToString::to_string).collect::<Vec<_>>().join(" and "),
-          pkg_manager
-        );
-    pkg_manager
+      "{}: Only one package manager should be used, but found {}.\n         Please remove unused package manager lock files, will use {} for now!",
+      "WARNING".yellow(),
+      found.iter().map(ToString::to_string).collect::<Vec<_>>().join(" and "),
+      pkg_manger
+    );
+    return pkg_manger;
+  }
+
+  if use_npm {
+    PackageManager::Npm
+  } else if use_pnpm {
+    PackageManager::Pnpm
+  } else if use_bun {
+    PackageManager::Bun
   } else {
-    package_managers
-      .into_iter()
-      .next()
-      .unwrap_or(PackageManager::Npm)
-  };
+    PackageManager::Yarn
+  }
+}
+
+pub fn items(app_dir: Option<&PathBuf>, metadata: &VersionMetadata) -> Vec<SectionItem> {
+  let mut package_manager = PackageManager::Npm;
+  if let Some(app_dir) = &app_dir {
+    let app_dir_entries = std::fs::read_dir(app_dir)
+      .unwrap()
+      .map(|e| e.unwrap().file_name().to_string_lossy().into_owned())
+      .collect::<Vec<String>>();
+    package_manager = get_package_manager(&app_dir_entries);
+  }
 
   if package_manager == PackageManager::Yarn
-    && yarn_version
+    && manager_version("yarn")
       .map(|v| v.chars().next().map(|c| c > '1').unwrap_or_default())
       .unwrap_or(false)
   {
@@ -178,46 +242,40 @@ pub fn items(
       ("@tauri-apps/cli", Some(metadata.js_cli.version.clone())),
     ] {
       let app_dir = app_dir.clone();
-      let item = SectionItem::new(
-        move || {
-          let version = version.clone().unwrap_or_else(|| {
-            npm_package_version(&package_manager, package, &app_dir)
-              .unwrap_or_default()
-              .unwrap_or_default()
-          });
-          let latest_ver = npm_latest_version(&package_manager, package)
+      let item = SectionItem::new().action(move || {
+        let version = version.clone().unwrap_or_else(|| {
+          npm_package_version(&package_manager, package, &app_dir)
             .unwrap_or_default()
-            .unwrap_or_default();
+            .unwrap_or_default()
+        });
+        let latest_ver = npm_latest_version(&package_manager, package)
+          .unwrap_or_default()
+          .unwrap_or_default();
+
+        if version.is_empty() {
+          format!("{} {}: not installed!", package, "".green())
+        } else {
+          format!(
+            "{} {}: {}{}",
+            package,
+            "[NPM]".dimmed(),
+            version,
+            if !(version.is_empty() || latest_ver.is_empty()) {
+              let version = semver::Version::parse(version.as_str()).unwrap();
+              let target_version = semver::Version::parse(latest_ver.as_str()).unwrap();
 
-          Some((
-            if version.is_empty() {
-              format!("{} {}: not installed!", package, "[NPM]".dimmed())
+              if version < target_version {
+                format!(" ({}, latest: {})", "outdated".yellow(), latest_ver.green())
+              } else {
+                "".into()
+              }
             } else {
-              format!(
-                "{} {}: {}{}",
-                package,
-                "[NPM]".dimmed(),
-                version,
-                if !(version.is_empty() || latest_ver.is_empty()) {
-                  let version = semver::Version::parse(version.as_str()).unwrap();
-                  let target_version = semver::Version::parse(latest_ver.as_str()).unwrap();
-
-                  if version < target_version {
-                    format!(" ({}, latest: {})", "outdated".yellow(), latest_ver.green())
-                  } else {
-                    "".into()
-                  }
-                } else {
-                  "".into()
-                }
-              )
-            },
-            Status::Neutral,
-          ))
-        },
-        || None,
-        false,
-      );
+              "".into()
+            }
+          )
+        }
+        .into()
+      });
 
       items.push(item);
     }

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels