mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-09-22 21:30:44 +02:00
refactor(updater): accomodate to new tauri config restructure (#924)
* refactor(updater): accomodate to new tauri config restructure RFC#5 https://github.com/tauri-apps/rfcs/blob/f3e82a6b0c5390401e855850d47dc7b7d9afd684/texts/0005-tauri-config-restructure.md RFC#5 PR implementation: https://github.com/tauri-apps/tauri/pull/8723 * lint and update configs
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
"updater": "patch"
|
||||||
|
---
|
||||||
|
|
||||||
|
Refactored the updater plugin to accommodate to the new changes in tauri config:
|
||||||
|
|
||||||
|
- JSON plugin config:
|
||||||
|
|
||||||
|
- Added `pubkey` option.
|
||||||
|
- Added `windows` option which is a Windows-specific options.
|
||||||
|
- Renamed `installer_args` to `installerArgs`.
|
||||||
|
- Moved `installerArgs` option to the new `windows` object.
|
||||||
|
|
||||||
|
- Rust crate changes:
|
||||||
|
- Added `pubkey` field for `Config`.
|
||||||
|
- Added `windows` field for `Config` option which is a Windows-specific options.
|
||||||
|
- Added `WindowsConfig` and `WindowsUpdateInstallMode`.
|
||||||
|
- Moved `installerArgs` option to the new `windows` object.
|
||||||
|
- Added `Builder::pubkey` and `UpdaterBuilder::pubkey`.
|
||||||
|
- Changed `Builder::installer_args` and `UpdaterBuilder::installer_args` to add to existing installer args, instead of replacing them, use `Builder/UpdaterBuilder::clear_installer_args` to clear existing args.
|
||||||
|
- Added `Builder::installer_arg`, `Builder::clear_installer_args`, `UpdaterBuilder::installer_arg` and `UpdaterBuilder::clear_installer_args`.
|
||||||
@@ -1,15 +1,40 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||||
|
"productName": "Tauri API",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"identifier": "com.tauri.api",
|
||||||
"build": {
|
"build": {
|
||||||
"distDir": "../dist",
|
"devUrl": "http://localhost:5173",
|
||||||
"devPath": "http://localhost:5173",
|
"frontendDist": "../dist",
|
||||||
"beforeDevCommand": "yarn dev",
|
"beforeDevCommand": "yarn dev",
|
||||||
"beforeBuildCommand": "yarn build",
|
"beforeBuildCommand": "yarn build"
|
||||||
"withGlobalTauri": true
|
|
||||||
},
|
},
|
||||||
"package": {
|
"app": {
|
||||||
"productName": "Tauri API",
|
"withGlobalTauri": true,
|
||||||
"version": "2.0.0"
|
"macOSPrivateApi": true,
|
||||||
|
"security": {
|
||||||
|
"pattern": {
|
||||||
|
"use": "isolation",
|
||||||
|
"options": {
|
||||||
|
"dir": "../isolation-dist/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"csp": {
|
||||||
|
"default-src": "'self' customprotocol: asset:",
|
||||||
|
"connect-src": "ipc: http://ipc.localhost",
|
||||||
|
"font-src": ["https://fonts.gstatic.com"],
|
||||||
|
"img-src": "'self' asset: http://asset.localhost blob: data:",
|
||||||
|
"style-src": "'unsafe-inline' 'self' http://fonts.googleapis.com"
|
||||||
|
},
|
||||||
|
"freezePrototype": true,
|
||||||
|
"assetProtocol": {
|
||||||
|
"enable": true,
|
||||||
|
"scope": {
|
||||||
|
"allow": ["$APPDATA/db/**", "$RESOURCE/**"],
|
||||||
|
"deny": ["$APPDATA/db/*.stronghold"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"cli": {
|
"cli": {
|
||||||
@@ -84,58 +109,27 @@
|
|||||||
"updater": {
|
"updater": {
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
"https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}"
|
"https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}"
|
||||||
]
|
],
|
||||||
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tauri": {
|
"bundle": {
|
||||||
"pattern": {
|
"active": true,
|
||||||
"use": "isolation",
|
"icon": [
|
||||||
"options": {
|
"icons/32x32.png",
|
||||||
"dir": "../isolation-dist/"
|
"icons/128x128.png",
|
||||||
}
|
"icons/128x128@2x.png",
|
||||||
},
|
"icons/icon.icns",
|
||||||
"macOSPrivateApi": true,
|
"icons/icon.ico"
|
||||||
"bundle": {
|
],
|
||||||
"active": true,
|
"windows": {
|
||||||
"identifier": "com.tauri.api",
|
"wix": {
|
||||||
"icon": [
|
"language": {
|
||||||
"icons/32x32.png",
|
"en-US": {},
|
||||||
"icons/128x128.png",
|
"pt-BR": {
|
||||||
"icons/128x128@2x.png",
|
"localePath": "locales/pt-BR.wxl"
|
||||||
"icons/icon.icns",
|
|
||||||
"icons/icon.ico"
|
|
||||||
],
|
|
||||||
"windows": {
|
|
||||||
"wix": {
|
|
||||||
"language": {
|
|
||||||
"en-US": {},
|
|
||||||
"pt-BR": {
|
|
||||||
"localePath": "locales/pt-BR.wxl"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"updater": {
|
|
||||||
"active": true,
|
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"windows": [],
|
|
||||||
"security": {
|
|
||||||
"csp": {
|
|
||||||
"default-src": "'self' customprotocol: asset:",
|
|
||||||
"connect-src": "ipc: http://ipc.localhost",
|
|
||||||
"font-src": ["https://fonts.gstatic.com"],
|
|
||||||
"img-src": "'self' asset: http://asset.localhost blob: data:",
|
|
||||||
"style-src": "'unsafe-inline' 'self' http://fonts.googleapis.com"
|
|
||||||
},
|
|
||||||
"freezePrototype": true,
|
|
||||||
"assetProtocol": {
|
|
||||||
"enable": true,
|
|
||||||
"scope": {
|
|
||||||
"allow": ["$APPDATA/db/**", "$RESOURCE/**"],
|
|
||||||
"deny": ["$APPDATA/db/*.stronghold"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +1,14 @@
|
|||||||
{
|
{
|
||||||
|
"productName": "deep-link-example",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"identifier": "com.tauri.deep-link-example",
|
||||||
"build": {
|
"build": {
|
||||||
|
"devUrl": "http://localhost:1420",
|
||||||
|
"frontendDist": "../dist",
|
||||||
"beforeDevCommand": "pnpm dev",
|
"beforeDevCommand": "pnpm dev",
|
||||||
"beforeBuildCommand": "pnpm build",
|
"beforeBuildCommand": "pnpm build"
|
||||||
"devPath": "http://localhost:1420",
|
|
||||||
"distDir": "../dist",
|
|
||||||
"withGlobalTauri": false
|
|
||||||
},
|
},
|
||||||
"package": {
|
"app": {
|
||||||
"productName": "deep-link-example",
|
|
||||||
"version": "0.0.0"
|
|
||||||
},
|
|
||||||
"tauri": {
|
|
||||||
"bundle": {
|
|
||||||
"active": true,
|
|
||||||
"category": "DeveloperTool",
|
|
||||||
"copyright": "",
|
|
||||||
"deb": {
|
|
||||||
"depends": []
|
|
||||||
},
|
|
||||||
"externalBin": [],
|
|
||||||
"icon": [
|
|
||||||
"icons/32x32.png",
|
|
||||||
"icons/128x128.png",
|
|
||||||
"icons/128x128@2x.png",
|
|
||||||
"icons/icon.icns",
|
|
||||||
"icons/icon.ico"
|
|
||||||
],
|
|
||||||
"identifier": "com.tauri.deep-link-example",
|
|
||||||
"longDescription": "",
|
|
||||||
"macOS": {
|
|
||||||
"entitlements": null,
|
|
||||||
"exceptionDomain": "",
|
|
||||||
"frameworks": [],
|
|
||||||
"providerShortName": null,
|
|
||||||
"signingIdentity": null
|
|
||||||
},
|
|
||||||
"resources": [],
|
|
||||||
"shortDescription": "",
|
|
||||||
"targets": "all",
|
|
||||||
"windows": {
|
|
||||||
"certificateThumbprint": null,
|
|
||||||
"digestAlgorithm": "sha256",
|
|
||||||
"timestampUrl": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": {
|
"security": {
|
||||||
"csp": null
|
"csp": null
|
||||||
},
|
},
|
||||||
@@ -68,5 +33,16 @@
|
|||||||
{ "host": "tauri.app" }
|
{ "host": "tauri.app" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"category": "DeveloperTool",
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../../node_modules/.pnpm/@tauri-apps+cli@2.0.0-alpha.17/node_modules/@tauri-apps/cli/schema.json",
|
"identifier": "studio.tauri.example",
|
||||||
"build": {
|
"build": {
|
||||||
"distDir": ".",
|
"frontendDist": ".",
|
||||||
"devPath": "http://localhost:4000"
|
"devUrl": "http://localhost:4000"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"app": {
|
||||||
"bundle": {
|
|
||||||
"identifier": "studio.tauri.example",
|
|
||||||
"active": true,
|
|
||||||
"icon": ["../../../examples/api/src-tauri/icons/icon.png"]
|
|
||||||
},
|
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "Tauri App"
|
"title": "Tauri App"
|
||||||
@@ -18,5 +13,9 @@
|
|||||||
"security": {
|
"security": {
|
||||||
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: http://tauri.localhost 'unsafe-eval' 'unsafe-inline' 'self'"
|
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: http://tauri.localhost 'unsafe-eval' 'unsafe-inline' 'self'"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"icon": ["../../../examples/api/src-tauri/icons/icon.png"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../../node_modules/.pnpm/@tauri-apps+cli@2.0.0-alpha.17/node_modules/@tauri-apps/cli/schema.json",
|
"identifier": "studio.tauri.example",
|
||||||
"build": {
|
"build": {
|
||||||
"distDir": ".",
|
"frontendDist": ".",
|
||||||
"devPath": "http://localhost:4000"
|
"devUrl": "http://localhost:4000"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"app": {
|
||||||
"bundle": {
|
|
||||||
"identifier": "studio.tauri.example",
|
|
||||||
"active": true,
|
|
||||||
"icon": ["../../../examples/api/src-tauri/icons/icon.png"]
|
|
||||||
},
|
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "Tauri App"
|
"title": "Tauri App"
|
||||||
@@ -18,5 +13,9 @@
|
|||||||
"security": {
|
"security": {
|
||||||
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: http://tauri.localhost 'unsafe-eval' 'unsafe-inline' 'self'"
|
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: http://tauri.localhost 'unsafe-eval' 'unsafe-inline' 'self'"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"icon": ["../../../examples/api/src-tauri/icons/icon.png"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,11 @@
|
|||||||
{
|
{
|
||||||
"package": {
|
"productName": "app",
|
||||||
"productName": "app",
|
"version": "0.1.0",
|
||||||
"version": "0.1.0"
|
"identifier": "com.tauri.single-instance",
|
||||||
},
|
|
||||||
"build": {
|
"build": {
|
||||||
"distDir": "../public",
|
"frontendDist": "../public"
|
||||||
"devPath": "../public"
|
|
||||||
},
|
},
|
||||||
"tauri": {
|
"app": {
|
||||||
"bundle": {
|
|
||||||
"active": true,
|
|
||||||
"targets": "all",
|
|
||||||
"identifier": "com.tauri.single-instance",
|
|
||||||
"icon": [
|
|
||||||
"icons/32x32.png",
|
|
||||||
"icons/128x128.png",
|
|
||||||
"icons/128x128@2x.png",
|
|
||||||
"icons/icon.icns",
|
|
||||||
"icons/icon.ico"
|
|
||||||
],
|
|
||||||
"resources": [],
|
|
||||||
"externalBin": [],
|
|
||||||
"copyright": "",
|
|
||||||
"category": "DeveloperTool",
|
|
||||||
"shortDescription": "",
|
|
||||||
"longDescription": "",
|
|
||||||
"deb": {
|
|
||||||
"depends": []
|
|
||||||
},
|
|
||||||
"macOS": {
|
|
||||||
"frameworks": [],
|
|
||||||
"exceptionDomain": "",
|
|
||||||
"signingIdentity": null,
|
|
||||||
"entitlements": null
|
|
||||||
},
|
|
||||||
"windows": {
|
|
||||||
"certificateThumbprint": null,
|
|
||||||
"digestAlgorithm": "sha256",
|
|
||||||
"timestampUrl": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "app",
|
"title": "app",
|
||||||
@@ -52,5 +18,16 @@
|
|||||||
"security": {
|
"security": {
|
||||||
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: http://tauri.localhost 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'"
|
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: http://tauri.localhost 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"targets": "all",
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,86 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use std::{ffi::OsString, fmt::Display};
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
/// Install modes for the Windows update.
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub enum WindowsUpdateInstallMode {
|
||||||
|
/// Specifies there's a basic UI during the installation process, including a final dialog box at the end.
|
||||||
|
BasicUi,
|
||||||
|
/// The quiet mode means there's no user interaction required.
|
||||||
|
/// Requires admin privileges if the installer does.
|
||||||
|
Quiet,
|
||||||
|
/// Specifies unattended mode, which means the installation only shows a progress bar.
|
||||||
|
Passive,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowsUpdateInstallMode {
|
||||||
|
/// Returns the associated `msiexec.exe` arguments.
|
||||||
|
pub fn msiexec_args(&self) -> &'static [&'static str] {
|
||||||
|
match self {
|
||||||
|
Self::BasicUi => &["/qb+"],
|
||||||
|
Self::Quiet => &["/quiet"],
|
||||||
|
Self::Passive => &["/passive"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the associated nsis arguments.
|
||||||
|
pub fn nsis_args(&self) -> &'static [&'static str] {
|
||||||
|
match self {
|
||||||
|
Self::Passive => &["/P", "/R"],
|
||||||
|
Self::Quiet => &["/S", "/R"],
|
||||||
|
_ => &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for WindowsUpdateInstallMode {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
Self::BasicUi => "basicUI",
|
||||||
|
Self::Quiet => "quiet",
|
||||||
|
Self::Passive => "passive",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WindowsUpdateInstallMode {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Passive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct WindowsConfig {
|
||||||
|
/// Additional arguments given to the NSIS or WiX installer.
|
||||||
|
#[serde(default)]
|
||||||
|
pub installer_args: Vec<OsString>,
|
||||||
|
/// Updating mode, see [`WindowsUpdateInstallMode`] for more info.
|
||||||
|
#[serde(default)]
|
||||||
|
pub install_mode: WindowsUpdateInstallMode,
|
||||||
|
}
|
||||||
|
|
||||||
/// Updater configuration.
|
/// Updater configuration.
|
||||||
#[derive(Debug, Clone, Deserialize, Default)]
|
#[derive(Debug, Clone, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
/// Updater endpoints.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub endpoints: Vec<UpdaterEndpoint>,
|
pub endpoints: Vec<UpdaterEndpoint>,
|
||||||
/// Additional arguments given to the NSIS or WiX installer.
|
/// Updater pubkey used to verify signatures.
|
||||||
#[serde(default, alias = "installer-args")]
|
pub pubkey: String,
|
||||||
pub installer_args: Vec<String>,
|
/// Updater config options specific to windows.
|
||||||
|
pub windows: Option<WindowsConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A URL to an updater server.
|
/// A URL to an updater server.
|
||||||
|
|||||||
@@ -25,9 +25,7 @@ pub enum Error {
|
|||||||
#[error("Could not fetch a valid release JSON from the remote")]
|
#[error("Could not fetch a valid release JSON from the remote")]
|
||||||
ReleaseNotFound,
|
ReleaseNotFound,
|
||||||
/// Unsupported app architecture.
|
/// Unsupported app architecture.
|
||||||
#[error(
|
#[error("Unsupported application architecture, expected one of `x86`, `x86_64`, `arm` or `aarch64`.")]
|
||||||
"Unsupported application architecture, expected one of `x86`, `x86_64`, `arm` or `aarch64`."
|
|
||||||
)]
|
|
||||||
UnsupportedArch,
|
UnsupportedArch,
|
||||||
/// Operating system is not supported.
|
/// Operating system is not supported.
|
||||||
#[error("Unsupported OS, expected one of `linux`, `darwin` or `windows`.")]
|
#[error("Unsupported OS, expected one of `linux`, `darwin` or `windows`.")]
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
|
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
use std::ffi::{OsStr, OsString};
|
||||||
|
|
||||||
use tauri::{
|
use tauri::{
|
||||||
plugin::{Builder as PluginBuilder, TauriPlugin},
|
plugin::{Builder as PluginBuilder, TauriPlugin},
|
||||||
Manager, Runtime,
|
Manager, Runtime,
|
||||||
@@ -69,15 +71,19 @@ impl<R: Runtime, T: Manager<R>> UpdaterExt<R> for T {
|
|||||||
fn updater_builder(&self) -> UpdaterBuilder {
|
fn updater_builder(&self) -> UpdaterBuilder {
|
||||||
let app = self.app_handle();
|
let app = self.app_handle();
|
||||||
let version = app.package_info().version.clone();
|
let version = app.package_info().version.clone();
|
||||||
let updater_config = app.config().tauri.bundle.updater.clone();
|
|
||||||
let UpdaterState { config, target } = self.state::<UpdaterState>().inner();
|
let UpdaterState { config, target } = self.state::<UpdaterState>().inner();
|
||||||
|
|
||||||
let mut builder = UpdaterBuilder::new(version, config.clone(), updater_config);
|
let mut builder = UpdaterBuilder::new(version, config.clone());
|
||||||
|
|
||||||
if let Some(target) = target {
|
if let Some(target) = target {
|
||||||
builder = builder.target(target);
|
builder = builder.target(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let args = self.env().args_os;
|
||||||
|
if !args.is_empty() {
|
||||||
|
builder = builder.installer_arg("/ARGS").installer_args(args);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
target_os = "linux",
|
target_os = "linux",
|
||||||
target_os = "dragonfly",
|
target_os = "dragonfly",
|
||||||
@@ -108,7 +114,8 @@ struct UpdaterState {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Builder {
|
pub struct Builder {
|
||||||
target: Option<String>,
|
target: Option<String>,
|
||||||
installer_args: Option<Vec<String>>,
|
pubkey: Option<String>,
|
||||||
|
installer_args: Vec<OsString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Builder {
|
impl Builder {
|
||||||
@@ -121,25 +128,50 @@ impl Builder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pubkey<S: Into<String>>(mut self, pubkey: S) -> Self {
|
||||||
|
self.pubkey.replace(pubkey.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn installer_args<I, S>(mut self, args: I) -> Self
|
pub fn installer_args<I, S>(mut self, args: I) -> Self
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = S>,
|
I: IntoIterator<Item = S>,
|
||||||
S: Into<String>,
|
S: AsRef<OsStr>,
|
||||||
{
|
{
|
||||||
self.installer_args
|
let args = args
|
||||||
.replace(args.into_iter().map(Into::into).collect());
|
.into_iter()
|
||||||
|
.map(|a| a.as_ref().to_os_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
self.installer_args.extend_from_slice(&args);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn installer_arg<S>(mut self, arg: S) -> Self
|
||||||
|
where
|
||||||
|
S: AsRef<OsStr>,
|
||||||
|
{
|
||||||
|
self.installer_args.push(arg.as_ref().to_os_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_installer_args(mut self) -> Self {
|
||||||
|
self.installer_args.clear();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build<R: Runtime>(self) -> TauriPlugin<R, Config> {
|
pub fn build<R: Runtime>(self) -> TauriPlugin<R, Config> {
|
||||||
|
let pubkey = self.pubkey;
|
||||||
let target = self.target;
|
let target = self.target;
|
||||||
let installer_args = self.installer_args;
|
let installer_args = self.installer_args;
|
||||||
PluginBuilder::<R, Config>::new("updater")
|
PluginBuilder::<R, Config>::new("updater")
|
||||||
.js_init_script(include_str!("api-iife.js").to_string())
|
.js_init_script(include_str!("api-iife.js").to_string())
|
||||||
.setup(move |app, api| {
|
.setup(move |app, api| {
|
||||||
let mut config = api.config().clone();
|
let mut config = api.config().clone();
|
||||||
if let Some(installer_args) = installer_args {
|
if let Some(pubkey) = pubkey {
|
||||||
config.installer_args = installer_args;
|
config.pubkey = pubkey;
|
||||||
|
}
|
||||||
|
if let Some(windows) = &mut config.windows {
|
||||||
|
windows.installer_args.extend_from_slice(&installer_args);
|
||||||
}
|
}
|
||||||
app.manage(UpdaterState { target, config });
|
app.manage(UpdaterState { target, config });
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
io::{Cursor, Read},
|
io::{Cursor, Read},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
@@ -20,14 +21,14 @@ use reqwest::{
|
|||||||
};
|
};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize};
|
use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize};
|
||||||
use tauri::{
|
use tauri::{utils::platform::current_exe, Resource};
|
||||||
utils::{config::UpdaterConfig, platform::current_exe},
|
|
||||||
Resource,
|
|
||||||
};
|
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::{
|
||||||
|
error::{Error, Result},
|
||||||
|
Config,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct ReleaseManifestPlatform {
|
pub struct ReleaseManifestPlatform {
|
||||||
@@ -89,8 +90,7 @@ impl RemoteRelease {
|
|||||||
|
|
||||||
pub struct UpdaterBuilder {
|
pub struct UpdaterBuilder {
|
||||||
current_version: Version,
|
current_version: Version,
|
||||||
config: crate::Config,
|
config: Config,
|
||||||
updater_config: UpdaterConfig,
|
|
||||||
version_comparator: Option<Box<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>>,
|
version_comparator: Option<Box<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>>,
|
||||||
executable_path: Option<PathBuf>,
|
executable_path: Option<PathBuf>,
|
||||||
target: Option<String>,
|
target: Option<String>,
|
||||||
@@ -98,19 +98,19 @@ pub struct UpdaterBuilder {
|
|||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
proxy: Option<Url>,
|
proxy: Option<Url>,
|
||||||
installer_args: Option<Vec<String>>,
|
installer_args: Vec<OsString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UpdaterBuilder {
|
impl UpdaterBuilder {
|
||||||
pub fn new(
|
pub fn new(current_version: Version, config: crate::Config) -> Self {
|
||||||
current_version: Version,
|
|
||||||
config: crate::Config,
|
|
||||||
updater_config: UpdaterConfig,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
|
installer_args: config
|
||||||
|
.windows
|
||||||
|
.as_ref()
|
||||||
|
.map(|w| w.installer_args.clone())
|
||||||
|
.unwrap_or_default(),
|
||||||
current_version,
|
current_version,
|
||||||
config,
|
config,
|
||||||
updater_config,
|
|
||||||
version_comparator: None,
|
version_comparator: None,
|
||||||
executable_path: None,
|
executable_path: None,
|
||||||
target: None,
|
target: None,
|
||||||
@@ -118,7 +118,6 @@ impl UpdaterBuilder {
|
|||||||
headers: Default::default(),
|
headers: Default::default(),
|
||||||
timeout: None,
|
timeout: None,
|
||||||
proxy: None,
|
proxy: None,
|
||||||
installer_args: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,20 +169,41 @@ impl UpdaterBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pubkey<S: Into<String>>(mut self, pubkey: S) -> Self {
|
||||||
|
self.config.pubkey = pubkey.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn installer_arg<S>(mut self, arg: S) -> Self
|
||||||
|
where
|
||||||
|
S: AsRef<OsStr>,
|
||||||
|
{
|
||||||
|
self.installer_args.push(arg.as_ref().to_os_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn installer_args<I, S>(mut self, args: I) -> Self
|
pub fn installer_args<I, S>(mut self, args: I) -> Self
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = S>,
|
I: IntoIterator<Item = S>,
|
||||||
S: Into<String>,
|
S: AsRef<OsStr>,
|
||||||
{
|
{
|
||||||
self.installer_args
|
let args = args
|
||||||
.replace(args.into_iter().map(Into::into).collect());
|
.into_iter()
|
||||||
|
.map(|a| a.as_ref().to_os_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
self.installer_args.extend_from_slice(&args);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_installer_args(mut self) -> Self {
|
||||||
|
self.installer_args.clear();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> Result<Updater> {
|
pub fn build(self) -> Result<Updater> {
|
||||||
let endpoints = self
|
let endpoints = self
|
||||||
.endpoints
|
.endpoints
|
||||||
.unwrap_or_else(|| self.config.endpoints.into_iter().map(|e| e.0).collect());
|
.unwrap_or_else(|| self.config.endpoints.iter().map(|e| e.0.clone()).collect());
|
||||||
|
|
||||||
if endpoints.is_empty() {
|
if endpoints.is_empty() {
|
||||||
return Err(Error::EmptyEndpoints);
|
return Err(Error::EmptyEndpoints);
|
||||||
@@ -207,13 +227,13 @@ impl UpdaterBuilder {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ok(Updater {
|
Ok(Updater {
|
||||||
config: self.updater_config,
|
config: self.config,
|
||||||
current_version: self.current_version,
|
current_version: self.current_version,
|
||||||
version_comparator: self.version_comparator,
|
version_comparator: self.version_comparator,
|
||||||
timeout: self.timeout,
|
timeout: self.timeout,
|
||||||
proxy: self.proxy,
|
proxy: self.proxy,
|
||||||
endpoints,
|
endpoints,
|
||||||
installer_args: self.installer_args.unwrap_or(self.config.installer_args),
|
installer_args: self.installer_args,
|
||||||
arch,
|
arch,
|
||||||
target,
|
target,
|
||||||
json_target,
|
json_target,
|
||||||
@@ -224,14 +244,14 @@ impl UpdaterBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Updater {
|
pub struct Updater {
|
||||||
config: UpdaterConfig,
|
config: Config,
|
||||||
current_version: Version,
|
current_version: Version,
|
||||||
version_comparator: Option<Box<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>>,
|
version_comparator: Option<Box<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>>,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
proxy: Option<Url>,
|
proxy: Option<Url>,
|
||||||
endpoints: Vec<Url>,
|
endpoints: Vec<Url>,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
installer_args: Vec<String>,
|
installer_args: Vec<OsString>,
|
||||||
arch: &'static str,
|
arch: &'static str,
|
||||||
// The `{{target}}` variable we replace in the endpoint
|
// The `{{target}}` variable we replace in the endpoint
|
||||||
target: String,
|
target: String,
|
||||||
@@ -336,8 +356,8 @@ impl Updater {
|
|||||||
|
|
||||||
let update = if should_update {
|
let update = if should_update {
|
||||||
Some(Update {
|
Some(Update {
|
||||||
current_version: self.current_version.to_string(),
|
|
||||||
config: self.config.clone(),
|
config: self.config.clone(),
|
||||||
|
current_version: self.current_version.to_string(),
|
||||||
target: self.target.clone(),
|
target: self.target.clone(),
|
||||||
extract_path: self.extract_path.clone(),
|
extract_path: self.extract_path.clone(),
|
||||||
installer_args: self.installer_args.clone(),
|
installer_args: self.installer_args.clone(),
|
||||||
@@ -360,7 +380,7 @@ impl Updater {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Update {
|
pub struct Update {
|
||||||
config: UpdaterConfig,
|
config: Config,
|
||||||
/// Update description
|
/// Update description
|
||||||
pub body: Option<String>,
|
pub body: Option<String>,
|
||||||
/// Version used to check for update
|
/// Version used to check for update
|
||||||
@@ -375,7 +395,7 @@ pub struct Update {
|
|||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
extract_path: PathBuf,
|
extract_path: PathBuf,
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
installer_args: Vec<String>,
|
installer_args: Vec<OsString>,
|
||||||
/// Download URL announced
|
/// Download URL announced
|
||||||
pub download_url: Url,
|
pub download_url: Url,
|
||||||
/// Signature announced
|
/// Signature announced
|
||||||
@@ -495,7 +515,7 @@ impl Update {
|
|||||||
// Update server can provide a custom EXE (installer) who can run any task.
|
// Update server can provide a custom EXE (installer) who can run any task.
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn install_inner(&self, bytes: Vec<u8>) -> Result<()> {
|
fn install_inner(&self, bytes: Vec<u8>) -> Result<()> {
|
||||||
use std::{ffi::OsStr, fs, process::Command};
|
use std::{fs, process::Command};
|
||||||
|
|
||||||
// FIXME: We need to create a memory buffer with the MSI and then run it.
|
// FIXME: We need to create a memory buffer with the MSI and then run it.
|
||||||
// (instead of extracting the MSI to a temp path)
|
// (instead of extracting the MSI to a temp path)
|
||||||
@@ -535,12 +555,21 @@ impl Update {
|
|||||||
installer_path.push("\"");
|
installer_path.push("\"");
|
||||||
|
|
||||||
let installer_args = [
|
let installer_args = [
|
||||||
self.config.windows.install_mode.nsis_args(),
|
self.config
|
||||||
|
.windows
|
||||||
|
.as_ref()
|
||||||
|
.map(|w| {
|
||||||
|
w.install_mode
|
||||||
|
.nsis_args()
|
||||||
|
.iter()
|
||||||
|
.map(|a| OsStr::new(a))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
self.installer_args
|
self.installer_args
|
||||||
.iter()
|
.iter()
|
||||||
.map(AsRef::as_ref)
|
.map(|a| a.as_os_str())
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>(),
|
||||||
.as_slice(),
|
|
||||||
]
|
]
|
||||||
.concat();
|
.concat();
|
||||||
|
|
||||||
@@ -552,7 +581,8 @@ impl Update {
|
|||||||
.arg(installer_path);
|
.arg(installer_path);
|
||||||
|
|
||||||
if !installer_args.is_empty() {
|
if !installer_args.is_empty() {
|
||||||
cmd.arg("-ArgumentList").arg(installer_args.join(", "));
|
cmd.arg("-ArgumentList")
|
||||||
|
.arg(installer_args.join(OsStr::new(", ")));
|
||||||
}
|
}
|
||||||
cmd.spawn().expect("installer failed to start");
|
cmd.spawn().expect("installer failed to start");
|
||||||
|
|
||||||
@@ -570,12 +600,21 @@ impl Update {
|
|||||||
msi_path.push("\"\"\"");
|
msi_path.push("\"\"\"");
|
||||||
|
|
||||||
let installer_args = [
|
let installer_args = [
|
||||||
self.config.windows.install_mode.msiexec_args(),
|
self.config
|
||||||
|
.windows
|
||||||
|
.as_ref()
|
||||||
|
.map(|w| {
|
||||||
|
w.install_mode
|
||||||
|
.msiexec_args()
|
||||||
|
.iter()
|
||||||
|
.map(|a| OsStr::new(a))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
self.installer_args
|
self.installer_args
|
||||||
.iter()
|
.iter()
|
||||||
.map(AsRef::as_ref)
|
.map(|a| a.as_os_str())
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>(),
|
||||||
.as_slice(),
|
|
||||||
]
|
]
|
||||||
.concat();
|
.concat();
|
||||||
|
|
||||||
@@ -591,7 +630,10 @@ impl Update {
|
|||||||
])
|
])
|
||||||
.arg("/i,")
|
.arg("/i,")
|
||||||
.arg(&msi_path)
|
.arg(&msi_path)
|
||||||
.arg(format!(", {}, /promptrestart;", installer_args.join(", ")))
|
.arg(format!(
|
||||||
|
", {}, /promptrestart;",
|
||||||
|
installer_args.join(OsStr::new(", ")).to_string_lossy()
|
||||||
|
))
|
||||||
.arg("Start-Process")
|
.arg("Start-Process")
|
||||||
.arg(current_exe_arg)
|
.arg(current_exe_arg)
|
||||||
.spawn();
|
.spawn();
|
||||||
@@ -636,10 +678,7 @@ impl Update {
|
|||||||
))]
|
))]
|
||||||
fn install_inner(&self, bytes: Vec<u8>) -> Result<()> {
|
fn install_inner(&self, bytes: Vec<u8>) -> Result<()> {
|
||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
use std::{
|
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||||
ffi::OsStr,
|
|
||||||
os::unix::fs::{MetadataExt, PermissionsExt},
|
|
||||||
};
|
|
||||||
let archive = Cursor::new(bytes);
|
let archive = Cursor::new(bytes);
|
||||||
let extract_path_metadata = self.extract_path.metadata()?;
|
let extract_path_metadata = self.extract_path.metadata()?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,28 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../../../node_modules/.pnpm/@tauri-apps+cli@2.0.0-alpha.17/node_modules/@tauri-apps/cli/schema.json",
|
"identifier": "com.tauri.updater",
|
||||||
"build": {
|
"plugins": {
|
||||||
"distDir": [],
|
"updater": {
|
||||||
"devPath": []
|
"endpoints": ["http://localhost:3007"],
|
||||||
},
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEMwNjY1MEExMTFBMDU5RTUKUldUbFdhQVJvVkJtd09sZ1ROT25yVGFhU2o0ZnUyd1FlT0ZTQ2ZXamN3SXk4SjZLZmNwRnV5dTMK",
|
||||||
"tauri": {
|
|
||||||
"bundle": {
|
|
||||||
"active": true,
|
|
||||||
"targets": "all",
|
|
||||||
"identifier": "com.tauri.updater",
|
|
||||||
"icon": [
|
|
||||||
"icons/32x32.png",
|
|
||||||
"icons/128x128.png",
|
|
||||||
"icons/128x128@2x.png",
|
|
||||||
"icons/icon.icns",
|
|
||||||
"icons/icon.ico"
|
|
||||||
],
|
|
||||||
"category": "DeveloperTool",
|
|
||||||
"windows": {
|
"windows": {
|
||||||
"wix": {
|
"installMode": "quiet"
|
||||||
"skipWebviewInstall": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"updater": {
|
|
||||||
"active": true,
|
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEMwNjY1MEExMTFBMDU5RTUKUldUbFdhQVJvVkJtd09sZ1ROT25yVGFhU2o0ZnUyd1FlT0ZTQ2ZXamN3SXk4SjZLZmNwRnV5dTMK",
|
|
||||||
"windows": {
|
|
||||||
"installMode": "quiet"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"plugins": {
|
"bundle": {
|
||||||
"updater": {
|
"active": true,
|
||||||
"endpoints": ["http://localhost:3007"]
|
"targets": "all",
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
],
|
||||||
|
"windows": {
|
||||||
|
"wix": {
|
||||||
|
"skipWebviewInstall": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,12 @@
|
|||||||
{
|
{
|
||||||
|
"identifier": "com.tauri.dev",
|
||||||
"build": {
|
"build": {
|
||||||
"distDir": "../build",
|
"devUrl": "http://localhost:5173/",
|
||||||
"devPath": "http://localhost:5173/",
|
"frontendDist": "../build",
|
||||||
"beforeDevCommand": "pnpm dev",
|
"beforeDevCommand": "pnpm dev",
|
||||||
"beforeBuildCommand": "pnpm build",
|
"beforeBuildCommand": "pnpm build"
|
||||||
"withGlobalTauri": false
|
|
||||||
},
|
},
|
||||||
"tauri": {
|
"app": {
|
||||||
"bundle": {
|
|
||||||
"active": true,
|
|
||||||
"targets": "all",
|
|
||||||
"identifier": "com.tauri.dev",
|
|
||||||
"icon": [
|
|
||||||
"icons/32x32.png",
|
|
||||||
"icons/128x128.png",
|
|
||||||
"icons/128x128@2x.png",
|
|
||||||
"icons/icon.icns",
|
|
||||||
"icons/icon.ico"
|
|
||||||
],
|
|
||||||
"resources": [],
|
|
||||||
"externalBin": [],
|
|
||||||
"copyright": "",
|
|
||||||
"category": "DeveloperTool",
|
|
||||||
"shortDescription": "",
|
|
||||||
"longDescription": "",
|
|
||||||
"deb": {
|
|
||||||
"depends": []
|
|
||||||
},
|
|
||||||
"macOS": {
|
|
||||||
"frameworks": [],
|
|
||||||
"minimumSystemVersion": "",
|
|
||||||
"exceptionDomain": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "Tauri App",
|
"title": "Tauri App",
|
||||||
@@ -45,5 +19,16 @@
|
|||||||
"security": {
|
"security": {
|
||||||
"csp": null
|
"csp": null
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"targets": "all",
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user